Actions

Workarounds: Manipulating a survey at runtime using Javascript: Difference between revisions

From LimeSurvey Manual

 
(109 intermediate revisions by 14 users not shown)
Line 8: Line 8:
<div class="simplebox"><center><br />'''Please keep in mind that these workarounds are not official LimeSurvey extensions - they are solutions that users have created for themselves.<br /><span style='color:#EE0000,#FFFFFF'>Therefore LimeSurvey can't offer guarantees or support for these solutions.</span><br />If you have questions, please contact the users that, thankfully, shared their solutions with the community.'''<br /><br /></center></div>
<div class="simplebox"><center><br />'''Please keep in mind that these workarounds are not official LimeSurvey extensions - they are solutions that users have created for themselves.<br /><span style='color:#EE0000,#FFFFFF'>Therefore LimeSurvey can't offer guarantees or support for these solutions.</span><br />If you have questions, please contact the users that, thankfully, shared their solutions with the community.'''<br /><br /></center></div>


<div class="simplebox">Note:  Version 1.92 implements many new features that make some existing workarounds obsolete.  You can continue to use existing workarounds, but may want to consider switching over to using the new features (since those new features are officially supported, and the workarounds are not).  Please review below for details of which are no longer needed.<br /><br /></div>
<div class="simplebox">Note:  ExpressionScript (EM) may cause some of these work-arounds to fail.  Any custom javascript included as a .js file will be fine.  However, if you in-line your JavaScript code, you must make sure that you have a space after every opening (left) curly brace, and a space before every closing (right) curly brace.  Otherwise, ExpressionScript (EM) will think that the content between the curly braces is something it should parse and understand.  I have fixed all of the work-arounds below that can be fixed (many did not follow those rules).  However, some will continue to break.  If a work-around contains a regular expression which contains curly braces (to indicate a range of repeats of a sub-expression), EM will try to process those curly braces, and thus break your regular expression.  Strategies to continue to support that work-around functionality are discussed below.<br /><br /></div>
 
<div class="simplebox">Also Note:  Version 1.92's Expression Manager (EM) may cause some of these work-arounds to fail.  Any custom javascript included as a .js file will be fine.  However, if you in-line your JavaScript code, you must make sure that you have a space after every opening (left) curly brace, and a space before every closing (right) curly brace.  Otherwise, EM will think that the content between the curly braces is something it should parse and understand.  I have fixed all of the work-arounds below that can be fixed (many did not follow those rules).  However, some will continue to break.  If a work-around contains a regular expression which contains curly braces (to indicate a range of repeats of a sub-expression), EM will try to process those curly braces, and thus break your regular expression.  Strategies to continue to support that work-around functionality are discussed below.<br /><br /></div>


__TOC__
__TOC__
Line 19: Line 17:


Finally it's imported to mark all your code with code tags, other wise you might break this page.
Finally it's imported to mark all your code with code tags, other wise you might break this page.
*Start tag: '''<nowiki><syntaxhighlight lang="php" enclose="div"></nowiki>'''
*Start tag: '''<nowiki><syntaxhighlight lang="php"></nowiki>'''
*End tag: '''<nowiki></syntaxhighlight></nowiki>'''.
*End tag: '''<nowiki></syntaxhighlight></nowiki>'''.


=How to ''use'' Script (eg. JavaScript etc.) in LimeSurvey?=
=How to ''use'' Script (eg. JavaScript etc.) in LimeSurvey=
 
Plugin [https://extensions.sondages.pro/questions-updating-and-managing/addscripttoquestion-easily-add-javascript/ addScriptToQuestion] allow to use a simple textarea for easily adding javascript.


To use JavaScript within LimeSurvey, the XSS filter must be turned off and the code inserted in the source of a question or a group description.
To use JavaScript within LimeSurvey, the XSS filter must be turned off and the code inserted in the source of a question or a group description.
Line 29: Line 29:
* Edit the question and click the "Source" button in the editor toolbar:
* Edit the question and click the "Source" button in the editor toolbar:


   [[File:editor_1.gif]]
   [[File:Editor_1.png]]
* Enter your script after the question text:
* Enter your script after the question text:


   [[File:editor_2.gif]]
   [[File:Editor_2.png]]
* Save the question
* Save the question


A simple test to see if JavaScript is enabled is to echo an alert. Use this code:
A simple test to see if JavaScript is enabled is to echo an alert. Use this code:


<syntaxhighlight lang="javascript" enclose="div"><script type="text/javascript" charset="utf-8">
<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">
  alert("Test!");
</script></syntaxhighlight>


  alert("Test!");
It is a good practice to use the jQuery $(document).on('ready pjax:complete') event to prevent the code from executing until the page is fully loaded, it is especially needed when using the ajax-mode in the 3.0.0 and upper default templates:


<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">
  $(document).on('ready pjax:complete',function() {
      alert("Test!");
  });
</script></syntaxhighlight>
</script></syntaxhighlight>


It is a good practice to use the jQuery $(document).ready function to prevent the code from executing until the page is fully loaded:
===Usage of brackets (<nowiki>{ and }</nowiki>)===
{{Alert|title=Caution when using brackets (<nowiki>{ and }</nowiki>) in scripts|text=[[ExpressionScript#Syntax|Expression manager]] uses brackets (<nowiki>{ and }</nowiki>) to enclose expressions. If you have to use brackets in your JavaScript, you must add a space or line feed after the opening bracket (<nowiki>{</nowiki>) and before the closing bracket (<nowiki>}</nowiki>) }}


<syntaxhighlight lang="javascript" enclose="div"><script type="text/javascript" charset="utf-8">
===Examples of usage of brackets===


   $(document).ready(function() {
ExpressionScript will try to parse this JavaScript:
 
<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">
      alert("Test!");
   $(function() {console.log('broken javascript');});
</script>
</syntaxhighlight>


Adding line feed prevents ExpressionScript from parsing
<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">
  $(function() {
      console.log('Adding a line feed for javascript');
   });
   });
</script>
</syntaxhighlight>


</script></syntaxhighlight>
ExpressionScript will try to parse this JavaScript:
<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">
  if(myvar==1) {console.log('broken javascript');};
</script>
</syntaxhighlight>


It is also good practice to use jslint.com to get a good layout and design (e.q. no cluttering of name space)
Adding spaces inside the brackets prevents ExpressionScript from parsing
 
<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">
==Usage of bracket (<nowiki>{ and }</nowiki>)==
  if(myvar==1) { console.log('Adding a space for javascript'); };
{{Alert|title=Caution with bracket ( <nowiki>{ and }</nowiki>)|text=[[Expression Manager#Syntax|Expression manager]] use bracket ( <nowiki>{ and }</nowiki>) to enclose expression. Then if you have to use bracket in your javascript, you must add space or line feed after starting bracket (<nowiki>{</nowiki>) and before closing bracket (<nowiki>}</nowiki>) }}
</script>
</syntaxhighlight>


=Sum up input values using Javascript=
=Sum up input values using Javascript=
Line 64: Line 84:
''Tested with: ''
''Tested with: ''


<div class="simplebox">As of version 1.92, this is easier to do using the sum() function in [[Expression Manager|Expression Manager]].  See this [[Expression Manager HowTos#Calculate assessment values at runtime and store the results in the survey data|HowTo example]].</div>
<div class="simplebox">As of version 1.92, this is easier to do using the sum() function in [[ExpressionScript|ExpressionScript]].  See this [[ExpressionScript how-tos#Calculate assessment values at runtime and store the results in the survey data|HowTo example]].</div>


<div class="simplebox">Starting in  Version 1.92, you also do no longer need to use SGQA (e.g. 45251X1X5) identifiers. Instead the code <code>{SGQ}</code> will automatically replaced by the current questions SGQA-codes.</div>
<div class="simplebox">Starting in  Version 1.92, you also do no longer need to use SGQA (e.g. 45251X1X5) identifiers. Instead the code <code>{SGQ}</code> will automatically replaced by the current questions SGQA-codes.</div>
Line 72: Line 92:
If you wonder what the {INSERTANS:1X6X12} stuff is, look at [http://docs.limesurvey.org/tiki-index.php?page=SGQA+Identifier SGQA identifier] and [[Adding a question#Information from previous answers|Using information from previous answers]].
If you wonder what the {INSERTANS:1X6X12} stuff is, look at [http://docs.limesurvey.org/tiki-index.php?page=SGQA+Identifier SGQA identifier] and [[Adding a question#Information from previous answers|Using information from previous answers]].


<syntaxhighlight lang="html4strict" enclose="div">
<syntaxhighlight lang="html4strict">


<div style="text-align: left">These are your answers:<br />
<div style="text-align: left">These are your answers:<br />
Line 240: Line 260:
''Tested with: ''
''Tested with: ''


<div class="simplebox">As of version 1.92, this is easier to do using the min/max/equals_num_value attributes (to validate sums), or the em_validation_q function, which lets you refer to other variables when you validate the current question [[Expression Manager|Expression Manager]].  Such validations also work on the current page, and properly handle array_filter and cascading relevance.</div>
<div class="simplebox">As of version 1.92, this is easier to do using the min/max/equals_num_value attributes (to validate sums), or the em_validation_q function, which lets you refer to other variables when you validate the current question [[ExpressionScript|ExpressionScript]].  Such validations also work on the current page, and properly handle array_filter and cascading relevance.</div>


This workaround is in case you need to do some validation involving more than one question, it implies a very little JavaScript programming and the use of SGQA Identifiers; this should work on almost any version of LimeSurvey.
This workaround is in case you need to do some validation involving more than one question, it implies a very little JavaScript programming and the use of SGQA Identifiers; this should work on almost any version of LimeSurvey.
Line 252: Line 272:
#Add a new non mandatory question, here is the trick the question text has to be (be sure to replace the SID, GID, QID1 and QID2
#Add a new non mandatory question, here is the trick the question text has to be (be sure to replace the SID, GID, QID1 and QID2


<syntaxhighlight lang="javascript" enclose="div">
<syntaxhighlight lang="javascript">


<script>
<script>
Line 296: Line 316:
This will work for 2 questions that you want to be the same.  Works well for email and confirm email questions
This will work for 2 questions that you want to be the same.  Works well for email and confirm email questions


<syntaxhighlight lang="php" enclose="div">
<syntaxhighlight lang="php">


  function validation() {
  function validation() {
Line 322: Line 342:
This script should be in the first question to be displayed in a next page after the question that should trigger the alternate exit. In this example the question that trigger the alternate exit is a Yes/No question, if the user chooses no, he is redirected to another page.
This script should be in the first question to be displayed in a next page after the question that should trigger the alternate exit. In this example the question that trigger the alternate exit is a Yes/No question, if the user chooses no, he is redirected to another page.


<syntaxhighlight lang="php" enclose="div">
<syntaxhighlight lang="php">


<script>
<script>
Line 348: Line 368:
To run any Javascript code right after page load use the Query ready() function. It will be executed when the page is being loaded. For example when you add the following code to one of your question definitions...:
To run any Javascript code right after page load use the Query ready() function. It will be executed when the page is being loaded. For example when you add the following code to one of your question definitions...:


<syntaxhighlight lang="php" enclose="div">
<syntaxhighlight lang="php">


<script>
<script>
Line 372: Line 392:
a) Hiding the form elements by calling in your Custom_On_Load:
a) Hiding the form elements by calling in your Custom_On_Load:


<syntaxhighlight lang="php" enclose="div">document.getElementById("answer73166X16X54").style.display='none';</syntaxhighlight>
<syntaxhighlight lang="php">document.getElementById("answer73166X16X54").style.display='none';</syntaxhighlight>


Note: As of svn build 9755, you can now dynamically reference the questions input fields by using the {SGQ} keyword. You can rewrite the previous example like this:
Note: As of svn build 9755, you can now dynamically reference the questions input fields by using the {SGQ} keyword. You can rewrite the previous example like this:


<syntaxhighlight lang="php" enclose="div">document.getElementById("answer{SGQ}").style.display='none';</syntaxhighlight>
<syntaxhighlight lang="php">document.getElementById("answer{SGQ}").style.display='none';</syntaxhighlight>


this makes it a little easier when exporting surveys, so you don't have to worry about manually entering each questions SurveyGroupQuestion ID.
this makes it a little easier when exporting surveys, so you don't have to worry about manually entering each questions SurveyGroupQuestion ID.
Line 382: Line 402:
b) Making the elements read only:
b) Making the elements read only:


<syntaxhighlight lang="php" enclose="div">document.getElementById("answer73166X16X54").readOnly=true;</syntaxhighlight> where the "answer73166X16X54" structure has been described in one of other workarounds above.
<syntaxhighlight lang="php">document.getElementById("answer73166X16X54").readOnly=true;</syntaxhighlight> where the "answer73166X16X54" structure has been described in one of other workarounds above.


c) For Hiding by a lot of elements by calling in your Customer_On_Load
c) For Hiding by a lot of elements by calling in your Customer_On_Load
Line 388: Line 408:
jQuery searchs in html document for input-fields with radio type, if his answer text empty then do the display css set to none.
jQuery searchs in html document for input-fields with radio type, if his answer text empty then do the display css set to none.


<syntaxhighlight lang="php" enclose="div">
<syntaxhighlight lang="php">


jQuery(document).ready(
jQuery(document).ready(
Line 438: Line 458:
There is an easy way to find out the current language and execute according Javascript using jQuery: The <html> tag of any survey pages contains an 'lang' attribute which holds the current language code. You can read that language code by using the followin code
There is an easy way to find out the current language and execute according Javascript using jQuery: The <html> tag of any survey pages contains an 'lang' attribute which holds the current language code. You can read that language code by using the followin code


<syntaxhighlight lang="javascript" enclose="div">
<syntaxhighlight lang="javascript">


jQuery(document).ready(
jQuery(document).ready(
Line 464: Line 484:
While this initially seems to be a bit tedious, it actually can be useful - you can alter your onload functions depending on the language in use. For example, you can dynamically display alerts or element contents in the current language.
While this initially seems to be a bit tedious, it actually can be useful - you can alter your onload functions depending on the language in use. For example, you can dynamically display alerts or element contents in the current language.


=Skipping the welcome page=
=Filter answers of a question with the answer of another=


''Tested with: 1.85 (second code snippet)''
===Method 1===


<div class="simplebox">Note: As of version 1.91, there is a survey option letting you skip the welcome page, so this workaround is not needed.</div>
''Tested with: 1.70 - 1.72''


'''Solution 1:''' Include this Javascript in your welcome template, in the file welcome.pstpl for all welcome page, or in the source of the welcome text for one survey:
<div class="simplebox">As of version 1.92, this is easier to do using the the array_filter attribute, or relevance equations in general.</div>


<syntaxhighlight lang="php" enclose="div">
If you want to filter the answer of a question with another answer, you can use this workaround.


<script>
First, use answer code like that: XX for the first question, and XXYYY for the second one.


jQuery(document).ready(function($) {
Answer of the second question filter if the 2 first letters are the answer of the first question.


document.limesurvey.move.value = 'movenext';
We use 1000X1X1 and 1000X1X2 for the example, question had to be on separate page (question by question for example).The second question had to be a dropdown list (we can do similar workaround, but droplis is the best). You can do this using JQuery.  It leaves the answer in the select box, but disables it.  You could also replace "disable" with "remove" to get rid of it altogether.


document.limesurvey.submit(); });
<syntaxhighlight lang="php">


</script>
var previousQuestionAnswer = "{INSERTANS:1000X1X1}";


</syntaxhighlight>
var currentQuestionID = "#answer1000X1X1"


It will reproduce the onclick event of the submit button and submit the form.
$(document).ready(function() {


Drawback of course is that users will see the page flashing up.
  // See if the answer for the previous question matches an answer in the current question


Therefore, you may want to announce "... loading survey ..." as a description of your welcome page (in the admin interface when creating the survey), to pretend loading something big.
  // (other than 'No Answer')


'''Solution 2:'''
  // If so, disable it.


<syntaxhighlight lang="php" enclose="div">
  if (previousQuestionAnswer!='No Answer') {


<script>
      $(currentQuestionID).children().each(function(index, Element)


function custom_on_load(){
      {


  document.limesurvey.move.value = 'movenext';
          if ($(this).attr('text') == previousQuestionAnswer)


  document.limesurvey.submit();
              { $(this).attr('disabled', true); }


}
          }


window.setTimeout( 'custom_on_load()',1);
      );}


</script>
}); // End of JQuery ready function


</syntaxhighlight>
</syntaxhighlight>


=Filter answers of a question with the answer of another=
===Method 2===


===Method 1===
''Tested with 1.92 + Firefox 9.0''


''Tested with: 1.70 - 1.72''
If you have a country, state, and city set of questions (or similar), you could code answer options as:


<div class="simplebox">As of version 1.92, this is easier to do using the the array_filter attribute, or relevance equations in general.</div>
'''Q1 Country''' ''Single choice list (ratio, dropdown)''


If you want to filter the answer of a question with another answer, you can use this workaround.
X country#


First, use answer code like that: XX for the first question, and XXYYY for the second one.
'''Q2 State''' ''Single choice list (dropdown)''


Answer of the second question filter if the 2 first letters are the answer of the first question.
XY state#


We use 1000X1X1 and 1000X1X2 for the example, question had to be on separate page (question by question for example).The second question had to be a dropdown list (we can do similar workaround, but droplis is the best). You can do this using JQuery.  It leaves the answer in the select box, but disables it.  You could also replace "disable" with "remove" to get rid of it altogether.
'''Q3 City''' ''Single choice list (dropdown)''


<syntaxhighlight lang="php" enclose="div">
XYZZ city#


var previousQuestionAnswer = "{INSERTANS:1000X1X1}";
Now you must put the following Javascript code in the source of question 2 (State):


var currentQuestionID = "#answer1000X1X1"
<syntaxhighlight lang="php"><script type="text/javascript" charset="utf-8">


$(document).ready(function() {
  $(document).ready(function() {


  // See if the answer for the previous question matches an answer in the current question
      // Get the countryCode (first character of the country)
      var countryCode = '{INSERTANS:1000X1X1}'.substr(0, 1).toUpperCase();


  // (other than 'No Answer')
      // Loop through all dropdown options and remove those with codes not starting with the countryCode
      $('select[id^="answer"] option').each(function(i){


  // If so, disable it.
          if($(this).attr('value')  && $(this).attr('value').substr(0, 1).toUpperCase() != countryCode) {
              $(this).remove();
          }
      });
  });


  if (previousQuestionAnswer!='No Answer') {
</script></syntaxhighlight>


      $(currentQuestionID).children().each(function(index, Element)
And the following code in the source of question 3 (City):


      {
<syntaxhighlight lang="php"><script type="text/javascript" charset="utf-8">


          if ($(this).attr('text') == previousQuestionAnswer)
  $(document).ready(function() {


              { $(this).attr('disabled', true); }
      // Get the stateCode (two first characters of the state)


          }
      var stateCode = '{INSERTANS:1000X1X2}'.substr(0, 2).toUpperCase();


       );}
       // Loop through all dropdown options and remove those with codes not starting with the stateCode


}); // End of JQuery ready function
      $('select[id^="answer"] option').each(function(i){


</syntaxhighlight>
          if($(this).attr('value')  && $(this).attr('value').substr(0, 2).toUpperCase() != stateCode) {


===Method 2===
              $(this).remove();


''Tested with 1.92 + Firefox 9.0''
          }


If you have a country, state, and city set of questions (or similar), you could code answer options as:
      });


'''Q1 Country''' ''Single choice list (ratio, dropdown)''
  });


X country#
</script></syntaxhighlight>


'''Q2 State''' ''Single choice list (dropdown)''
Each question must be in different pages of survey to work, i.e. in a question by question survey, or in diferent groups in a group by group survey.


XY state#
Note that if you choose a two characters answer option code in the State question, i.e. a XYY answer option code, you must change the ''substr(0, 2)'' part of code in question 3 to ''substr(0, 3)''.


'''Q3 City''' ''Single choice list (dropdown)''
=Filter answers of a multiple numeric type question=


XYZZ city#
''Tested with: 1.90+''


Now you must put the following Javascript code in the source of question 2 (State):
<div class="simplebox">As of version 1.92, this is easier to do using the the array_filter attribute, or relevance equations in general.  In 1.92, array_filter works with all questions that have subquestions (e.g. also works for multiple numeric and multiple short text).</div>


<syntaxhighlight lang="php" enclose="div"><script type="text/javascript" charset="utf-8">
If you want to filter a question of type "multiple numeric", you have to modify the workaround.


  $(document).ready(function() {
Note that this workaround assumes that the code for each of the subquestions are a numeric sequence 1,2,3...


      // Get the countryCode (first character of the country)
<syntaxhighlight lang="php">
      var countryCode = '{INSERTANS:1000X1X1}'.substr(0, 1).toUpperCase();


      // Loop through all dropdown options and remove those with codes not starting with the countryCode
<script type="text/javascript" charset="utf-8">
      $('select[id^="answer"] option').each(function(i){


          if($(this).attr('value') && $(this).attr('value').substr(0, 1).toUpperCase() != countryCode) {
  $(document).ready(function(){
              $(this).remove();
          }
      });
  });


</script></syntaxhighlight>
  // Match elements with labels using htmlFor and a loop


And the following code in the source of question 3 (City):
  var labels = document.getElementsByTagName('LABEL');


<syntaxhighlight lang="php" enclose="div"><script type="text/javascript" charset="utf-8">
  for (var i = 0; i < labels.length; i++) {


  $(document).ready(function() {
      if (labels[i].htmlFor != '') {


      // Get the stateCode (two first characters of the state)
            var elem = document.getElementById(labels[i].htmlFor);


      var stateCode = '{INSERTANS:1000X1X2}'.substr(0, 2).toUpperCase();
            if (elem)


      // Loop through all dropdown options and remove those with codes not starting with the stateCode
              elem.label = labels[i];


       $('select[id^="answer"] option').each(function(i){
       }


          if($(this).attr('value')  && $(this).attr('value').substr(0, 2).toUpperCase() != stateCode) {
  }


              $(this).remove();
  var answerFilter="{INSERTANS:96553X63X759}";


          }
  for (i=0; i<30; i++){


      });
    var iLabel = document.getElementById('answer96553X63X760'+(i+1]].label.innerHTML;


  });
    if (answerFilter.search(iLabel) == -1){


</script></syntaxhighlight>
      // Hide element


Each question must be in different pages of survey to work, i.e. in a question by question survey, or in diferent groups in a group by group survey.
      $('#question760').find('li').eq(i).hide();


Note that if you choose a two characters answer option code in the State question, i.e. a XYY answer option code, you must change the ''substr(0, 2)'' part of code in question 3 to ''substr(0, 3)''.
      // Set value of hidden elements to zero


=Filter answers of a multiple numeric type question=
      document.getElementById('answer96553X63X760'+(i+1)).value = 0;


''Tested with: 1.90+''
    }


<div class="simplebox">As of version 1.92, this is easier to do using the the array_filter attribute, or relevance equations in general.  In 1.92, array_filter works with all questions that have sub-questions (e.g. also works for multiple numeric and multiple short text).</div>
  }


If you want to filter a question of type "multiple numeric", you have to modify the workaround.
});</script>


Note that this workaround assumes that the code for each of the subquestions are a numeric sequence 1,2,3...
</syntaxhighlight>


<syntaxhighlight lang="php" enclose="div">
=Use an answer to prefill another question (default value)=


<script type="text/javascript" charset="utf-8">
''Tested with: 1.72''


$(document).ready(function(){
''And tested with: 1.91+''


  // Match elements with labels using htmlFor and a loop
<div class="simplebox">As of version 1.92, this is easier to do using the enhanced defaults feature, which lets you pre-fill most question types with default values.  In 1.91, you could set defaults for list and multiple choice questions.  In 1.92, you can set defaults for any text-entry question too.  Moreover, the defaults can be [[ExpressionScript|ExpressionScript]] compatible equations or tailored text.  Finally, with 1.92, defaults are properly managed even when set on the same page.</div>


  var labels = document.getElementsByTagName('LABEL');
This workaround gives you the ablility to use an answer to fill the default value for another question. It's explained for free text question type but can be adapted for another question type.


  for (var i = 0; i < labels.length; i++) {
We use SGQA identifier. For example, we use SGQA 1000X10X20 and 1000X10X21 to prefill 1000X11X30 and 1000X11X31. For the script to work, the question has to be on a different page.


      if (labels[i].htmlFor != '') {
<syntaxhighlight lang="php"><script type="text/javascript" charset="utf-8">


            var elem = document.getElementById(labels[i].htmlFor);
$(document).ready(function() {


            if (elem)
$('#answer1000X11X30').val('{INSERTANS:1000X10X20}');


              elem.label = labels[i];
$('#answer1000X11X31').val('{INSERTANS:1000X10X21}');


      }
});


  }
</script></syntaxhighlight>


  var answerFilter="{INSERTANS:96553X63X759}";
If you want use two times the value of an integer input, you can also use this to prefill a question that you hide via CSS.


  for (i=0; i<30; i++){
<syntaxhighlight lang="php"><script type="text/javascript" charset="utf-8">


    var iLabel = document.getElementById('answer96553X63X760'+(i+1]].label.innerHTML;
$(document).ready(function() {


    if (answerFilter.search(iLabel) == -1){
$('#answer1000X2X2').val(parseInt('{INSERTANS:1000X1X1}')*2);


      // Hide element
$("#question2").hide();


      $('#question760').find('li').eq(i).hide();
});


      // Set value of hidden elements to zero
</script>


      document.getElementById('answer96553X63X760'+(i+1)).value = 0;
</syntaxhighlight>


    }
Now you can use {INSERTANS:1000X2X2} for giving you twice the value of the user input from question 1.


  }
=Dynamic Sum=


  });</script>
<div class="simplebox">As of version 1.92, this is easier to do with [[ExpressionScript|ExpressionScript]] using the sum() function.  Not only will sums be dynamic, but they wille exclude irrelevant values, so you can array_filter a multiple numeric question and get the sum of just the answers that are visible. This [[ExpressionScript how-tos#Calculate assessment values at runtime and store the results in the survey data|HowTo example]] demonstrates the dynamic sum feature.</div>


</syntaxhighlight>
<div class="simplebox">Also note, as of version 1.92, the following workaround will fail, since it includes a a regular expression containing curly braces.</div>


=Use an answer to prefill another question (default value)=
This script is used with a "Multiple Options" type question calculate a total price based on how which checkboxes are selected. The prices are listed as part of the answer. The answers are set up like this: "USB Mouse ($20)". The script uses the number between the "($" and the ")" as the price for that item. You can adapt the script to if you need to change the currency or need to sum something other than money.


''Tested with: 1.72''
This script works when used in a question-by-question format.  It does not work when there are more than one question on a page. I think it can be adapted to work, but I haven't done the coding yet.


''And tested with: 1.91+''
Put the following code into body of your question:


<div class="simplebox">As of version 1.92, this is easier to do using the enhanced defaults feature, which lets you pre-fill most question types with default values.  In 1.91, you could set defaults for list and multiple choice questions.  In 1.92, you can set defaults for any text-entry question too.  Moreover, the defaults can be [[Expression Manager|Expression Manager]] compatible equations or tailored text.  Finally, with 1.92, defaults are properly managed even when set on the same page.</div>
<syntaxhighlight lang="php">


This workaround gives you the ablility to use an answer to fill the default value for another question. It's explained for free text question type but can be adapted for another question type.
<script language=Javascript>


We use SGQA identifier. For example, we use SGQA 1000X10X20 and 1000X10X21 to prefill 1000X11X30 and 1000X11X31. For the script to work, the question has to be on a different page.
// General definitions


<syntaxhighlight lang="php" enclose="div"><script type="text/javascript" charset="utf-8">
// Define a regular expression to match the labels containing information about an answer


$(document).ready(function() {
// In LimeSurvey, these labels start with "answer..."


$('#answer1000X11X30').val('{INSERTANS:1000X10X20}');
var answerRegex = /^answer/;


$('#answer1000X11X31').val('{INSERTANS:1000X10X21}');
// Find all answer checkboxes in the document and set their


});
// onchange events to updatePrice


</script></syntaxhighlight>
function Custom_On_Load()


If you want use two times the value of an integer input, you can also use this to prefill a question that you hide via CSS.
{


<syntaxhighlight lang="php" enclose="div"><script type="text/javascript" charset="utf-8">
  // Find all input elements


$(document).ready(function() {
  var inputList=document.getElementsByTagName("input");


$('#answer1000X2X2').val(parseInt('{INSERTANS:1000X1X1}')*2);
  // Loop through each, looking for checkboxes


$("#question2").hide();
  var i;


});
  for( i=0; i< inputList.length;i++ )


</script>
  {


</syntaxhighlight>
      // If input item does not have an ID, skip


Now you can use {INSERTANS:1000X2X2} for giving you twice the value of the user input from question 1.
      if (! (inputList[i].id)) { continue; }


=Dynamic Sum=
      // Skip to next if ID doesn't start with "answer"


<div class="simplebox">As of version 1.92, this is easier to do with [[Expression Manager|Expression Manager]] using the sum() function.  Not only will sums be dynamic, but they wille exclude irrelevant values, so you can array_filter a multiple numeric question and get the sum of just the answers that are visible.  This [[Expression Manager HowTos#Calculate assessment values at runtime and store the results in the survey data|HowTo example]] demonstrates the dynamic sum feature.</div>
      if (!answerRegex.test(inputList[i].id)) { continue; }


<div class="simplebox">Also note, as of version 1.92, the following workaround will fail, since it includes a a regular expression containing curly braces.</div>
      // Skip to next if not a checkbox


This script is used with a "Multiple Options" type question calculate a total price based on how which checkboxes are selected. The prices are listed as part of the answer. The answers are set up like this: "USB Mouse ($20)". The script uses the number between the "($" and the ")" as the price for that item. You can adapt the script to if you need to change the currency or need to sum something other than money.
      if (inputList[i].type.toUpperCase()!='CHECKBOX') { continue; }


This script works when used in a question-by-question format.  It does not work when there are more than one question on a page. I think it can be adapted to work, but I haven't done the coding yet.
      // This is an answer checkbox!


Put the following code into body of your question:
      // Setup onchange event


<syntaxhighlight lang="php" enclose="div">
      inputList[i].onclick = updatePrice;


<script language=Javascript>
  }


// General definitions
      // Load initial sum


// Define a regular expression to match the labels containing information about an answer
  updatePrice();


// In LimeSurvey, these labels start with "answer..."
}


var answerRegex = /^answer/;
function updatePrice()


// Find all answer checkboxes in the document and set their
{


// onchange events to updatePrice
var labels=document.getElementsByTagName("LABEL");


function Custom_On_Load()
// Define a regular expression to match a price inside parenthesis


{
// For example: ($29) or ($29.54).


  // Find all input elements
// Set up


  var inputList=document.getElementsByTagName("input");
priceRegex = /\(\$(\d*\.{0,1}\d+)\)/;


  // Loop through each, looking for checkboxes
// Loop through all the labels, looking for labels corresponding to


  var i;
// answers that have a price in them.


  for( i=0; i< inputList.length;i++ )
var i;


  {
var total = 0;


      // If input item does not have an ID, skip
for( i=0; i<labels.length;i++ )


      if (! (inputList[i].id)) { continue; }
{


      // Skip to next if ID doesn't start with "answer"
  var myArray;    // Define array used for regex matching


      if (!answerRegex.test(inputList[i].id)) { continue; }
  var inputID;   // Define field element


      // Skip to next if not a checkbox
  // Get the ID of the field corresponding to the label using the


      if (inputList[i].type.toUpperCase()!='CHECKBOX')  { continue; }
  // "htmlFor" attribute


      // This is an answer checkbox!
  // (FYI, there are some labels that will have to be screened out


      // Setup onchange event
  // because they don't correspond to an answer)


      inputList[i].onclick = updatePrice;
  // Does the label start with "answer"?


   }
   // If not: exit.


      // Load initial sum
  if (!answerRegex.test(labels[i].htmlFor)) { continue; }


   updatePrice();
   // First make sure this element exists


}
  // If it doesn't, go to the next element


function updatePrice()
  // If it does, it will be defined in inputID


{
  if (! (inputID = document.getElementById(labels[i].htmlFor)) ) { continue; }


var labels=document.getElementsByTagName("LABEL");
  // See if the corresponding answer is a checkbox that is checked.


// Define a regular expression to match a price inside parenthesis
  // If not, go to next label


// For example: ($29) or ($29.54).
  if (inputID.type.toUpperCase()!='CHECKBOX') { continue; }


// Set up
  if (!inputID.checked) { continue; }


priceRegex = /\(\$(\d*\.{0,1}\d+)\)/;
  // This is label for an answer.


// Loop through all the labels, looking for labels corresponding to
  // The innerHTML will give us the text of the label


// answers that have a price in them.
  // The price information will be in the form ($XX.XX)


var i;
  // which in contained in the label text.


var total = 0;
  // Find a match for a decimal number inside the pattern "($" and ")"


for( i=0; i<labels.length;i++ )
  if [[myArray = priceRegex.exec(labels[i].innerHTML]] != null)


{
  {


  var myArray;    // Define array used for regex matching
      // Keep a running tally


  var inputID;   // Define field element
      total += parseFloat(myArray[1]);


   // Get the ID of the field corresponding to the label using the
   }


  // "htmlFor" attribute
}


  // (FYI, there are some labels that will have to be screened out
  // Update total price on form


  // because they don't correspond to an answer)
  document.getElementById("totalPrice").value = "$" + formatCurrency(total);


  // Does the label start with "answer"?
}


  // If not: exit.
function formatCurrency(num) {


  if (!answerRegex.test(labels[i].htmlFor)) { continue; }
  num = isNaN(num) || num <u> '' || num </u> null ? 0.00 : num;


  // First make sure this element exists
  return parseFloat(num).toFixed(2);


  // If it doesn't, go to the next element
}


  // If it does, it will be defined in inputID
</script>


  if (! (inputID = document.getElementById(labels[i].htmlFor)) ) { continue; }
</syntaxhighlight>


  // See if the corresponding answer is a checkbox that is checked.
Put the following code in the help section (or wherever you want the sum to appear):


  // If not, go to next label
<syntaxhighlight lang="php">Total Cost for selected accessories: <input type="readonly" value="" id="totalPrice" /></syntaxhighlight>


  if (inputID.type.toUpperCase()!='CHECKBOX')  { continue; }
=Focus on the first Input field=


  if (!inputID.checked) { continue; }
(''Tested with: LimeSurvey 1.80 / IE6/7, Firefox 3, Safari, Opera, chrome'')


  // This is label for an answer.
<div class="simplebox">As of version 1.91, there is a built-in JavaScript function called  focusFirst() to focus on the first visible element.  It uses jQuery, so is more robust than this example.</div>


  // The innerHTML will give us the text of the label
Again an onload Function is needed for this tasks. I have done it the DOM way with compatibility to IE6/7.


  // The price information will be in the form ($XX.XX)
Put the following code into the startpage.pstpl of your template, right before the </head> closing tag:


  // which in contained in the label text.
<syntaxhighlight lang="php">


  // Find a match for a decimal number inside the pattern "($" and ")"
<script type="text/javascript">


   if [[myArray = priceRegex.exec(labels[i].innerHTML]] != null)
   function focusFirst(Event)


   {
   {


       // Keep a running tally
       var i=0;


       total += parseFloat(myArray[1]);
       while(document.forms[0].elements[i].type == "hidden")


  }
      {
 
          i++;


}
      }


  // Update total price on form
      document.forms[0].elements[i].focus();


  document.getElementById("totalPrice").value = "$" + formatCurrency(total);
      return;


}
  }


function formatCurrency(num) {
  if(ie)


  num = isNaN(num) || num <u> '' || num </u> null ? 0.00 : num;
  { window.attachEvent("onload", focusFirst); }


  return parseFloat(num).toFixed(2);
  else


}
  { document.addEventListener("load", focusFirst, true); }


</script>
</script>
Line 898: Line 920:
</syntaxhighlight>
</syntaxhighlight>


Put the following code in the help section (or wherever you want the sum to appear):
'''Note:''' I am using this with version 1.80. As changes happened to the templates in the last release, you might need to add the following before the code, if your template is older than 1.80 stable and does not include this:


<syntaxhighlight lang="php" enclose="div">Total Cost for selected accessories: <input type="readonly" value="" id="totalPrice" /></syntaxhighlight>
<syntaxhighlight lang="php">


=Focus on the first Input field=
<script type="text/javascript">


(''Tested with: LimeSurvey 1.80 / IE6/7, Firefox 3, Safari, Opera, chrome'')
  var ie = false;


<div class="simplebox">As of version 1.91, there is a built-in JavaScript function called  focusFirst() to focus on the first visible element.  It uses jQuery, so is more robust than this example.</div>
</script>


Again an onload Function is needed for this tasks. I have done it the DOM way with compatibility to IE6/7.


Put the following code into the startpage.pstpl of your template, right before the </head> closing tag:


<syntaxhighlight lang="php" enclose="div">
</syntaxhighlight>


<script type="text/javascript">
You will need this for the var ie to be set. Without this var, the function will not work with ie.


  function focusFirst(Event)
=Answer questions by keypress=


  {
(''Tested with: IE7, Firefox 3'')


      var i=0;
This workaround allow to answer the question on key 0-9 (0 is for the 10 answer).


      while(document.forms[0].elements[i].type == "hidden")
Follow issues are supported at the moment:
*Yes-No Questions
*List Questions (radio)
*other radio questions


      {
In lists the answer limit is ten.


          i++;
You supply the template "endpage" of your template with the following script.


      }
<syntaxhighlight lang="php">


      document.forms[0].elements[i].focus();
<script type="text/javascript">


      return;
jQuery(document).ready(function() {


   }
   jQuery(document).bind('keypress', function(e){


  if(ie)
      if (48 <= e.which && e.which <= 57) {


  { window.attachEvent("onload", focusFirst); }
          jQuery('input:radio').each(function() {


  else
              var v = jQuery(this).val();


  { document.addEventListener("load", focusFirst, true); }
              var c = (e.which <u> 48) ? 10 : e.which - 48;


</script>
              if(v </u> 'Y' || v <u> 'N') {


</syntaxhighlight>
                  v = (v </u> 'Y') ? 1 : 2;


'''Note:''' I am using this with version 1.80. As changes happened to the templates in the last release, you might need to add the following before the code, if your template is older than 1.80 stable and does not include this:
              }


<syntaxhighlight lang="php" enclose="div">
              if(v == c) {


<script type="text/javascript">
                  jQuery(this).click();


  var ie = false;
                  jQuery('#limesurvey input:submit').click();


</script>
              }


          });


      }


</syntaxhighlight>
  })


You will need this for the var ie to be set. Without this var, the function will not work with ie.
});


=Answer questions by keypress=
</script>


(''Tested with: IE7, Firefox 3'')
</syntaxhighlight>


This workaround allow to answer the question on key 0-9 (0 is for the 10 answer).
=Math notation in LimeSurvey=
#download the js file from [http://mathcs.chapman.edu/~jipsen/mathml/asciimathdownload.html AsciiMath script]
#copy it in the /scripts/ folder in your installation
#modify the startpage.psptl of the template you want to use adding the reference to the script (if you use an older version make sure that you copy the template folder first!) for example:


Follow issues are supported at the moment:
<syntaxhighlight lang="php">
*Yes-No Questions
*List Questions (radio)
*other radio questions


In lists the answer limit is ten.
<script type="text/javascript" src="ASCIIMathML.js">


You supply the template "endpage" of your template with the following script.
</syntaxhighlight>


<syntaxhighlight lang="php" enclose="div">
Use the notation where you need it!


<script type="text/javascript">
'''Note:''' If you use internet explorer or safari you will need to download the plugin as well, but firefox does the lot for you.


jQuery(document).ready(function() {
Finally, if you are confident with the limesurvey structure you could add the editor which support math notation and change the reference to the editor you want to use in the configuration file. here is the [http://www.imathas.com/editordemo/demo.html link for a TinyMCE with math support].


  jQuery(document).bind('keypress', function(e){
=Filter "Array by Column" Question With "Multiple-Options"=


      if (48 <= e.which && e.which <= 57) {
<div class="simplebox">As of version 2.06 this workaround is not required. It can be handled with subquestion relevance.</div>


          jQuery('input:radio').each(function() {
''Tested with: 2.05''


              var v = jQuery(this).val();
This workaround applies a filter to an "Array by Column" question based on the checked boxes of a previous multiple-options question. There are two methods - one for filter and array questions being on the same page and a slightly different one for the filter question being on a page before the array question.


              var c = (e.which <u> 48) ? 10 : e.which - 48;
Implementation is as follows.


              if(v </u> 'Y' || v <u> 'N') {
'''FOR BOTH METHODS:'''
*[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
*If using LimeSurvey version 2.05 or newer, place the following functions in your template.js file. These will be accessed by either method.<syntaxhighlight lang="javascript">function filterArrByCol(qMultiOpt, qArray, prevPage) {


                  v = (v </u> 'Y') ? 1 : 2;
// If filter question is on a previous page, hide Q1 and check all "visible" boxes
 
if(prevPage == 1) {
              }
$('#question'+qMultiOpt+' li[id^="javatbd"]:visible input.checkbox').attr('checked', true);
 
$('#question'+qMultiOpt+'').hide();
              if(v == c) {
}
 
 
                  jQuery(this).click();
// Assign classes to the answer cells
 
$('#question'+qArray+' td.answer-item').each(function(i){
                  jQuery('#limesurvey input:submit').click();
var classArr = $(this).attr('class').split('answer_cell_00');
classArr = classArr[1].split(' ');
var ansCode = classArr[0];
$(this).addClass('ans-'+ansCode+' filtered');
});
 
// Assign classes to the answer label cells
$('#question'+qArray+' table.subquestions-list tbody tr:eq(0) td.answer-item').each(function(i){
var classArr2 = $(this).attr('class').split(' ans-');
var ansCode2 = classArr2[1];
$('#question'+qArray+' table.subquestions-list thead tr:eq(0) th:eq('+i+')').addClass('ans-'+ansCode2+'');
});
 
// Fire the filter function on page load
filterArr(qMultiOpt, qArray);


              }
// Listener on multi-opt checkboxes to fire the filter function
$('#question'+qMultiOpt+' input.checkbox').click(function(){
filterArr(qMultiOpt, qArray);
});
 
// On submit, clear all hidden radios of array
$('#movenextbtn, #movesubmitbtn').click(function(){
$('#question'+qArray+' td.filtered:hidden').each(function(i){
$('input.radio', this).prop('checked', false);
});
return true;
});
}


          });
function filterArr(qMultiOpt, qArray) {


      }
if($('#question'+qMultiOpt+' input.checkbox:checked').length < 1) {
 
// Hide the array if no multi-opt options are checked
  })
$('#question'+qArray+'').hide();
 
}
});
else {
 
$('#question'+qArray+'').show();
</script>
 
// Hide all columns of array
$('#question'+qArray+' table.subquestions-list tbody td.answer-item, #question'+qArray+' table.question thead th').hide();
 
// Loop through multi-opt checkboxes and, if checked, show corresponding column of array
$('#question'+qMultiOpt+' input.checkbox').each(function(i){
if($(this).prop('checked') == true) {
var classArr3 = $(this).attr('id').split('X'+qMultiOpt);
var ansCode3 = classArr3[1];
$('#question'+qArray+' .ans-'+ansCode3+'').show();
}
});
}
}</syntaxhighlight>


</syntaxhighlight>
*If using LimeSurvey version 2.0 or older, place the following functions in your template.js file. These will be accessed by either method.<syntaxhighlight lang="javascript">
$(document).ready(function() {


=Math notation in LimeSurvey=
function filterArrByCol(qMultiOpt, qArray, prevPage) {
#download the js file from [http://mathcs.chapman.edu/~jipsen/mathml/asciimathdownload.html AsciiMath script]
#copy it in the /scripts/ folder in your installation
// If filter question is on a previous page, hide Q1 and check all "visible" boxes
#modify the startpage.psptl of the template you want to use adding the reference to the script (if you use an older version make sure that you copy the template folder first!) for example:
if(prevPage == 1) {
$('#question'+qMultiOpt+' li[id^="javatbd"]:visible input.checkbox').attr('checked', true);
$('#question'+qMultiOpt+'').hide();
}
// Assign classes to the answer cells
$('#question'+qArray+' table.question tbody td').each(function(i){
var classArr = $(this).attr('class').split('answer_cell_00');
var ansCode = classArr[1];
$(this).addClass('ans-'+ansCode+' filtered');
});
// Assign classes to the answer label cells
$('#question'+qArray+' table.question tbody tr:eq(0) td').each(function(i){
var classArr2 = $(this).attr('class').split(' ans-');
var ansCode2 = classArr2[1];
$('#question'+qArray+' table.question thead tr:eq(0) th:eq('+i+')').addClass('ans-'+ansCode2+'');
});
// Fire the filter function on page load
filterArr(qMultiOpt, qArray);


<syntaxhighlight lang="php" enclose="div">
// Listener on multi-opt checkboxes to fire the filter function
$('#question'+qMultiOpt+' input.checkbox').click(function(){
filterArr(qMultiOpt, qArray);
});
// On submit, clear all hidden radios of array
$('#movenextbtn, #movesubmitbtn').click(function(){
$('#question'+qArray+' td.filtered:hidden').each(function(i){
$('input.radio', this).attr('checked', false);
});
return true;
});
}
function filterArr(qMultiOpt, qArray) {
if($('#question'+qMultiOpt+' input.checkbox:checked').length < 1) {
// Hide the array if no multi-opt options are checked
$('#question'+qArray+'').hide();
}
else {
$('#question'+qArray+'').show();
// Hide all columns of array
$('#question'+qArray+' table.question tbody td, #question'+qArray+' table.question thead th').hide();
// Loop through multi-opt checkboxes and, if checked, show corresponding column of array
$('#question'+qMultiOpt+' input.checkbox').each(function(i){
if($(this).attr('checked') == true) {
var classArr3 = $(this).attr('id').split('X'+qMultiOpt);
var ansCode3 = classArr3[1];
$('#question'+qArray+' .ans-'+ansCode3+'').show();
}
});
}
}
});</syntaxhighlight>


<script type="text/javascript" src="ASCIIMathML.js">
'''FOR BOTH FILTER AND ARRAY QUESTIONS ON SAME PAGE:'''
 
*Create a multiple options question and an array-by-column question on the same page (in the same group).
</syntaxhighlight>
*Both questions '''MUST''' have identical subquestions and subquestion codes.
*Place the following script in the source of one of the questions.
*Replace "MM" with the ID of the multiple options question and "AA" with the ID of the ranking question.
*The function will hide the array question unless at least one option is selected in the multiple-options question, in which case, it will only show the corresponding array column(s).<syntaxhighlight lang="javascript">
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
filterArrByCol(MM, AA);
});
</script></syntaxhighlight>


Use the notation where you need it!
'''FOR FILTER AND ARRAY QUESTIONS ON SEPARATE PAGES:'''
*Create a multiple options question on page 1.
*Create a multiple options question and an array-by-column question on page 2.
*All three questions '''MUST''' have identical subquestions and subquestion codes.
*Set the multiple options on page 2 to be filtered by the multiple options on page 1.
*Place the following script in the source of one of the questions on page 2.
*Replace "MM" with the ID of the multiple options question and "AA" with the ID of the array question. Do not modify the "1".
*The function will hide the multiple options question. If options were selected in the multiple options on page 1, the corresponding options will be checked in the hidden multiple options question which will then be used to control the display of the array columns.<syntaxhighlight lang="javascript">
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
filterArrByCol(MM, AA, 1);
});
</script></syntaxhighlight>


'''Note:''' If you use internet explorer or safari you will need to download the plugin as well, but firefox does the lot for you.
=Auto-tabbing between text input fields=


Finally, if you are confident with the limesurvey structure you could add the editor which support math notation and change the reference to the editor you want to use in the configuration file. here is the [http://www.imathas.com/editordemo/demo.html link for a TinyMCE with math support].
''Tested with: 1.90+, IE 6/7, FireFox 3.0, Safari 3.2, Chrome 2.0''


=Reverse array_filter (for pre-1.90)=
This is a workaround to facilitate auto-tabbing between text input questions in a single group using a [http://plugins.jquery.com/project/autotab jQuery plugin by Mathachew] and the Maximum characters attribute of questions.


''Tested with: 1.85+ (7253), IE 6/7, FireFox 3.0, Safari 3.2, Chrome 2.0''
The behaviour will be: When the maximum number of characters has been entered into an input field, the focus (cursor) will be automatically moved to the next input field. When the backspace key is used to remove all characters from a field, the focus will be moved to the previous field. It's very useful in surveys having many text input questions to avoid having to manually tab or click between fields.


<div class="simplebox">Note: Version 1.91 natively supports this functionality via the array_filter_exclude advanced quation option.</div>
Implementation is as follows:
#Visit [http://plugins.jquery.com/autotabs http://plugins.jquery.com/autotabs], download the plugin script and save it as ''jquery.autotab-1.1b.js'' in the template folder.
#Link to the script by placing ''<script type="text/javascript" src="{TEMPLATEURL}jquery.autotab-1.1b.js"></script>'' within the ''<head>'' tag of startpage.pstpl.
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
#Create the text based questions that auto-tabbing is to be applied to. (All must be in the same group if you're using group by group presentation)
#Define a Maximum characters attribute for all questions that the autotab is applied to.
{{QS:maximum_chars}} 
#In the source of the first question of the group that contains the auto-tabbed questions, add the following code.


This can be used to filter the answers of an array question to be displayed based on the answers of a multi option question that were '''NOT''' selected. A possible application would be to have a multi-option question asking which products were purchased, followed by 2 array questions - one asking to rate the purchased products and another asking why the remaining products were not purchased. Another would be to ask about sessions attended at a conference as in [http://www.limesurvey.org/en/forum/5-can-i-do-this-with-limesurvey/27970-reverse-of-arrayfilter.html this forum thread] and pictured below. There is a live [http://www.partnersinc.biz/surveys//index.php?sid=43358&newtest=Y&lang=en demo here]
<syntaxhighlight lang="php"><script type="text/javascript" charset="utf-8">


[[File:sessions_645x344.gif]]
  $(document).ready(function() {


We'll use the above example to explain the process as follows:
      $('#answerSSSSSXGGXAA').focus(); //initially focus on the first input
#In group 1 - Create a ''SessionsAttended'' multi-options question
#In group 1 - Create a ''SessionsNotAttended'' multi-options question with identical answers as ''SessionsAttended'' and hide it with styles or conditions
#In group 2 - Create a ''RateSessionsAttended'' array question with an [http://docs.limesurvey.org/tiki-index.php?page=Question+attributes&structure;=English+Instructions+for+LimeSurvey#array_filter array_filter] based on ''SessionsAttended''
#In group 2 - Create a ''ReasonNotAttended'' array question with an [http://docs.limesurvey.org/tiki-index.php?page=Question+attributes&structure;=English+Instructions+for+LimeSurvey#array_filter array_filter] based on ''SessionsNotAttended''
#In group 1 - Place JavaScript/jQeury listeners on each of the checkboxes in ''SessionsAttended'' that would toggle the corresponding checkbox in ''SessionsNotAttended'' to the opposite state


The JavaScript/jQuery code used would be:
      $('#answerSSSSSXGGXAA').autotab({ target: 'answerSSSSSXGGXBB' });


<syntaxhighlight lang="php" enclose="div">
      $('#answerSSSSSXGGXBB').autotab({ previous: 'answerSSSSSXGGXAA', target: 'answerSSSSSXGGXCC' });


       // The toggle function
       $('#answerSSSSSXGGXCC').autotab({ previous: 'answerSSSSSXGGXBB', target: 'answerSSSSSXGGXDD' });


  function toggle(q1, q2) {
      $('#answerSSSSSXGGXDD').autotab({ previous: 'answerSSSSSXGGXCC' });


      if ($(q1).attr('checked') == false ) {
  });


          $(q2).attr('checked', true);
</script></syntaxhighlight>


      }
This example has 4 fields to be auto-tabbed between - all instances of the following must be replaced to be compatible with your survey (See image below):
 
*SSSSS -> Survey ID
      else {
*GG -> Group ID
*AA -> First question
*BB -> Second question
*CC -> Third question
*DD -> Last question


          $(q2).attr('checked', false);
[[File:ids_343x252.gif]]


      }
The ''target'' parameter defines where the next input is and the ''previous'' parameter defines (you guessed it) the previous input.


  }
This plugin also has limited capability of formatting the input text - see comments in the plugin script.


  // Initialize the SessionNotAttended checkboxes to "checked"
=Custom response listeners=


  toggle ('#answer11111X22X331', '#answer11111X22X441');
''Tested with: 1.85+ (7253), IE 6/7, FireFox 3.0, Safari 3.2, Chrome 2.0''


  toggle ('#answer11111X22X332', '#answer11111X22X442');
Listeners can be used to perform functions when a change in an input is detected. There are many uses such as the [[Workarounds#Reverse_array_filter|Reverse array_filter]] but a simple one would be to warn a respondent if they enter a value that may be too high.


  toggle ('#answer11111X22X333', '#answer11111X22X443');
We can use the [http://docs.jquery.com/Events/change jQuery change( ) event] to fire a function whenever there is a change to the input. The function will look for an input value higher than 10 and, if found, will warn the respondent.
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
#Place the script below in the source of one of the questions on the page where:
**11111 is the survey ID
**22 is the group ID
**33 is the question ID


  toggle ('#answer11111X22X334', '#answer11111X22X444');
<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">


  // The jQuery listeners on the SessionAttended checkboxes
$(document).ready(function() {


  // that toggle the SessionNotAttended checkboxes
$('#answer11111X22X33').change(function() {


  $('#answer11111X22X331').change(function() {
if ( $('#answer11111X22X33').val() > 10 ) {
alert ("That's an awful lot. Are you sure?");
}
});
});
</script></syntaxhighlight>


      toggle ('#answer11111X22X331', '#answer11111X22X441');
=Text input masks=


  });
''Tested with: 1.90+, IE 7, FireFox 3.0, Safari 3.2, Chrome 2.0''


  $('#answer11111X22X332').change(function() {
There may be instances where you would like to place a mask on a text input - for example only allowing respondents to enter the correct characters for a North American phone number ((###) ###-####) or a Canadian postal code (A1A 1A1) The [http://plugins.jquery.com/project/meioMask jQuery meioMask plugin by fabiomcosta] can be used to do this.


      toggle ('#answer11111X22X332', '#answer11111X22X442');
The plugin has several pre-defined masks and supports custom masks. It also allows for pasting of input, allows default values and can auto-focus the next form element when the current input is completely filled. See [http://www.meiocodigo.com/projects/meiomask/ http://www.meiocodigo.com/projects/meiomask/] for more details on features and options.


  });
Implementation for this demo was as follows:
#Visit [http://www.meiocodigo.com/projects/meiomask/ http://www.meiocodigo.com/projects/meiomask/], download the latest plugin script and save it as jquery.meiomask.js in the template folder.
#Link to the script by placing <script type="text/javascript" src="{TEMPLATEURL}jquery.meiomask.js" charset="utf-8"></script> within the <head> tag of startpage.pstpl.
#Turn off $filterxsshtml to allow insertion of JavaScript in the questions. ([http://docs.limesurvey.org/tiki-index.php?page=Optional+settings&structure=English+Instructions+for+LimeSurvey#Filtering_dangerous_HTML_tags_in_survey_objects See documentation here])
#Create the text input questions that the masks are to be applied to
#In the source of the help section of the first question of the group that contains the masked inputs, add the following code. ([http://docs.limesurvey.org/tiki-index.php?page=Workarounds&structure=English+Instructions+for+LimeSurvey#How_to_use_Script_eg._JavaScript_etc._ See How to use script here])  


  $('#answer11111X22X333').change(function() {


      toggle ('#answer11111X22X333', '#answer11111X22X443');
<syntaxhighlight lang="javascript">
<script type="text/javascript" charset="utf-8">


  });
$(document).ready(function() {
 
 
  $('#answer11111X22X334').change(function() {
// Define custom masks
$.mask.masks = $.extend($.mask.masks,{
customMask1:{ mask: 'a9a 9a9' }, // Canadian postal code
// Maximum input of 9,999.99, reverse input, default value of 0.00
customMask2:{ mask: '99.999,9', type : 'reverse', defaultValue : '000' }
});
// Set the 'alt' attributes of the inputs
$('#answer111111X22XAA').attr('alt', 'phone-us'); // a pre-defined mask  
$('#answer111111X22XBB').attr('alt', 'customMask1'); // a custom mask  
$('#answer111111X22XCC').attr('alt', 'customMask2'); // a custom mask  
$('#answer111111X22XDD').attr('alt', 'cc'); // a pre-defined mask for credit card
// Tell the plugin to apply masks to these inputs
$('input:#answer111111X22XAA').setMask();
$('input:#answer111111X22XBB').setMask();
$('input:#answer111111X22XCC').setMask();
$('input:#answer111111X22XDD').setMask();
});
</script>
</syntaxhighlight>


      toggle ('#answer11111X22X334', '#answer11111X22X444');


  });
The first section defines two custom masks to be used - one for Canadian postal code with a format of A#A #A# and a second for a maximum amount of 9999.99 with reverse input (useful for cost inputs) and a default value of 0.00.


</syntaxhighlight>
The next section sets the "alt" attributes for the text inputs. This will tell meioMask which mask we will be applying to each input. By default meioMask looks at the alt attribute to find which mask to apply however this can be changed using the [http://www.meiocodigo.com/projects/meiomask/#mm_options mask options].


Some notes about the code:
In the final section we apply the masks to the inputs.
*The code is in an onload function defined in the first question of group 1 - see [[Workarounds#Custom_onload_function|here]]
*Both ''SessionsAttended'' and ''SessionsNotAttended'' must be in the same group for the toggling to work
*This case only uses 4 checkboxes but could be expanded as required
*In this case the survey ID is 11111, the group ID is 22, ''SessionsAttended'' ID is 33 and SessionsNotAttended ID is 44 - these would need to be modified for your survey
*The toggle function looks for the state of a checkbox in ''SessionsAttended'' and sets the corresponding checkbox in ''SessionsNotAttended'' to the opposite state
*The "Initialize" section sets the state of ''SessionsNotAttended'' checkboxes whenever we navigate to the group
*The "Listener" section uses jQuery listeners to detect any change in the ''SessionsAttended'' checkboxes and then toggle the corresponding ''SessionsNotAttended'' checkbox


Some general notes about the questions:
This example has 4 inputs with masks on them - all instances of the following must be replaced to be compatible with your survey (See image below):
*CSS can be used to hide ''SessionsNotAttended'' (div#question44 { display: none; }) if you don't want to use conditions (because maybe you've got [[Optional settings#Survey Behavior|$deletenonvalues set to 1]])
*''RateSessionsAttended'' and ''ReasonNotAttended'' are another group to avoid things popping in and out of existence as ''SessionsAttended'' is answered
*[[Setting conditions|Conditions]] are used to ensure that ''RateSessionsAttended'' only appears if some sessions are selected and ''ReasonNotAttended'' only appears if no sessions are selected


=Filter Ranking Question With Multiple-Options (pre 1.92)=
*11111 -> Survey ID
*22 -> Group ID
*AA -> First question
*BB -> Second question
*CC -> Third question
*DD -> Fourth question


''Tested with: 1.90, IE 7/8, FireFox 3/4, Safari 5.0, Chrome 10.0''
[[File:Ids 343x252.gif]]


<div class="simplebox">As of version 1.92, most of this is easier to do using the the array_filter attribute, or relevance equations in general.  They all work on the same page, and fully support cascading relevance.</div>
=Text input masks - second method=


This workaround applies a filter to a ranking question based on the checked boxes of a previous multiple-options question. There are two methods - one for filter and ranking questions being on the same page and a slightly different one for the filter question being on a page before the ranking question.
''Tested with: 1.91RC6+, FireFox 4.0''
''Tested with: 2.05, Chrome 40.0.2214.115 m (64-bit)


There is a [http://www.partnersinc.biz/surveys//index.php?sid=96314&newtest=Y&lang=en demo of both methods here].
There may be instances where you would like to place a mask on a text input - for example only allowing respondents to enter the correct characters for a North American phone number ((###) ###-####) or a US social security number (###-##-####). The jQuery meioMask plugin by fabiomcosta is no longer supported but an alternative exists. The DigitalBush Masked Input Plugin is updated and simple to implement in LimeSurvey. See [http://digitalbush.com/projects/masked-input-plugin/ http://digitalbush.com/projects/masked-input-plugin/] for more details on features and options.


Implementation is as follows.
Implementation as follows:
 
#Visit [http://digitalbush.com/projects/masked-input-plugin/ http://digitalbush.com/projects/masked-input-plugin/], download the latest plugin script and save it as jquery.maskedinput.js in the template folder.
#Link to the script by placing <script type="text/javascript" src="{TEMPLATEURL}jquery.maskedinput.js" charset="utf-8"></script> within the <head> tag of startpage.pstpl.
#Turn off $filterxsshtml to allow insertion of JavaScript in the questions. ([http://docs.limesurvey.org/tiki-index.php?page=Optional+settings&structure=English+Instructions+for+LimeSurvey#Filtering_dangerous_HTML_tags_in_survey_objects See documentation here])
#Create the text input questions that the masks are to be applied to
#In the source of the help section of the first question of the group that contains the masked inputs, add the following code. ([http://docs.limesurvey.org/tiki-index.php?page=Workarounds&structure=English+Instructions+for+LimeSurvey#How_to_use_Script_eg._JavaScript_etc._ See How to use script here]) 


'''FOR BOTH METHODS:'''
*[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
*Place the following script in your template.js file. This function will be accessed by either method.<syntaxhighlight lang="javascript" enclose="div">
// A function to filter choices in a ranking question
function rankFilter(q1ID, q2ID, prevPage) {


// If filter question is on a previous page, hide Q1 and check all "visible" boxes
<syntaxhighlight lang="javascript">
if(prevPage == 1) {
<script type="text/javascript" charset="utf-8">
$('#question'+q1ID+' li[id^="javatbd"]:visible input.checkbox').attr('checked', true);
$(document).ready(function($) {
$('#question'+q1ID+'').hide();
$("#answer55431X1X5").mask("999-99-9999",{ placeholder:"#" });
}
handleChecked(q1ID, q2ID);
$('#question'+q1ID+' input.checkbox').click(function() {
handleChecked(q1ID, q2ID);
});
});
</script>
function handleChecked(q1ID, q2ID) {
</syntaxhighlight>
 
// Find the survey and group IDs
 
if($( 'input#fieldnames' ).length != 0) {
Replace the "#answer55431X1X5" with your survey ID, group ID, and question ID. You can change the mask format and change or eliminate the placeholder if you wish. If you have multiple questions in the same group, simply copy and paste the line that begins with "$" and change the code to what is needed for your application.
var fieldNames = $( 'input#fieldnames' ).attr('value');
 
var tmp = fieldNames.split('X');
=Select a random response to a "Multiple options" question for later use=
var sID = tmp[0];
 
var gID = tmp[1];
''Tested with: LimeSurvey versions 2.06 and 2.5, IE 7-11, Firefox, Chrome''
}
 
This workaround allows you to randomly select one of the checked boxes in a "Multiple options" question and store it's label for later use in the survey. A possible use-case would be to have a question asking what products were purchased and then randomly selecting one of the products to ask further questions about.
 
The workaround puts a listener on the checkboxes that loops through all of the checked boxes and adds their labels to an array. A random item is pulled from the array and loaded into the hidden question. This hidden question can then be used in ExpressionScript equations, relevance or conditions.
<br /><br />
'''DOWNLOAD:'''<br />
- [[Media:Demo_Random_Option_From_Multi-Choice_v206.1.zip|Demo survey for LS 2.06]]<br />
- [[Media:Demo_Random_Option_From_Multi-Choice_v25.1.zip|Demo survey for LS 2.5]]<br />
<br />
'''IMPLEMENTATION''' (LimeSurvey version 2.06)<br />
 
#[https://manual.limesurvey.org/Workarounds:_Manipulating_a_survey_at_runtime_using_Javascript#How_to_use_Script_.28eg._JavaScript_etc..29_in_LimeSurvey.3F Set up your survey to use JavaScript].
#Create a "Multiple options" question
#Immediately following that question, create a short-text question to store the label of the checked box in (we'll hide this with JavaScript)
#Place the script below in the source of the multiple options question. <syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">
$(document).ready(function() {
// Identify the questions
var thisQuestion = $('#question{QID}');
var qHidden = thisQuestion.nextAll('.text-short:eq(0)');
var hiddenInput = $('input.text', qHidden);
// Loop through all Q1 options
// Hide qHidden
$('#question'+q1ID+' input.checkbox').each(function(i) {
qHidden.hide();
// Find the answer code and value
// Listener on the checkboxes
var tmp2 = $(this).attr('id').split('X'+gID+'X'+q1ID+'');
$('input.checkbox', thisQuestion).on('change', function(e) {
var ansCode = tmp2[1];
handleChecked();
var ansTxt = $(this).next('label').text();
// If option is checked and not in rank choices or output, add it to the rank choices
if($(this).attr('checked') == true
  && $('#question'+q2ID+' select.select option[value="'+ansCode+'"]').length == 0
  && $('#question'+q2ID+' .output input[id^="fvalue_"][value="'+ansCode+'"]').length == 0) {
$('<option value="'+ansCode+'">'+ansTxt+'</option>').appendTo('#question'+q2ID+' select.select');
}
// If option is unchecked...
else if($(this).attr('checked') == false) {
// Remove it from the rank choices
$('#question'+q2ID+' select.select option[value="'+ansCode+'"]').remove();
// Remove it from the rank output and reset hidden values
$('#question'+q2ID+' .output input[id^="fvalue_"][value="'+ansCode+'"]').attr('value', '').siblings('input.text').val('').siblings('img').hide();
}
});
});
// Clean up empty inputs in the rank output table
// Listener on the "Other" input
$('#question'+q2ID+' .output table tr').each(function(i) {
$('input.text', thisQuestion).on('keyup change', function(e) {
var nextRow = $(this).next('tr');
setTimeout(function() {
if($('input.text', this).val() == '' && $('input.text', nextRow).val() != '') {
handleChecked();
$('input.text', this).val($('input.text', nextRow).val());
}, 250);
$('input.text', nextRow).val('');
$('input[id^="fvalue_"]', this).attr('value', $('input[id^="fvalue_"]', nextRow).attr('value'));
$('input[id^="fvalue_"]', nextRow).attr('value', '');
}
});
});
// Show the scissors for the last populated rank output row
function handleChecked() {
$('#question'+q2ID+' .output table img[id^="cut_"]').hide();
// Build an array of checked answers
$('#question'+q2ID+' .output table input.text[value!=""]:last').siblings('img[id^="cut_"]').show();
var checkedAnswers = [];
$('input.checkbox:checked', thisQuestion).each(function(i) {
if($(this).closest('.answer-item').hasClass('other-item')) {
checkedAnswers.push($(this).closest('.other-item').find('input.text').val());
}
else {
checkedAnswers.push($(this).nextAll('label:eq(0)').text());
}
});
// Load the hidden question with a random item from the array
var checkedLength = checkedAnswers.length;
$(hiddenInput).val(checkedAnswers[Math.floor(Math.random()*checkedLength)]);
// Fire ExpressionScript
checkconditions(hiddenInput.value, hiddenInput.name, hiddenInput.type);
}
    });
</script></syntaxhighlight>
<br />
'''IMPLEMENTATION''' (LimeSurvey version 2.5)
 
#Follow the first 3 implementation steps above
#Place the script below in the source of the multiple options question.<syntaxhighlight lang="javascript">
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
// Identify the questions
var thisQuestion = $('#question{QID}');
var qHidden = thisQuestion.nextAll('.text-short:eq(0)');
var hiddenInput = $('input.text', qHidden);
// Hide extra rank output rows
// Hide qHidden
var optNum = $('#question'+q1ID+' input.checkbox:checked').length;
qHidden.hide();
$('#question'+q2ID+' .output table tr').hide();
$('#question'+q2ID+' .output table tr:lt('+(optNum+1)+')').show();
// Hide and clear the ranking question if there are less than 2 checked options in Q1
// Class for "Other
if(optNum < 2) {
$('input.text', thisQuestion).closest('.answer-item').addClass('other-item');
$('#question'+q2ID+'').hide();
$('#question'+q2ID+' input.text').val('');
$('#question'+q2ID+' input[id^="fvalue_"]').attr('value', '');
}
else {
$('#question'+q2ID+'').show();
}
// A workaround for the built in max-answers function
// Listener on the checkboxes
$('#question'+q2ID+' select.select').attr('disabled', true);
$('input.checkbox', thisQuestion).on('change', function(e) {
$('#question'+q2ID+' td.output tr:visible').each(function(i) {
handleChecked();
if($('input.text', this).val() == '') {
$('#question'+q2ID+' select.select').attr('disabled', false);
}
});
});
}
// Listener on the "Other" input
// A listener to work around the built in max-answers function
$('input.text', thisQuestion).on('keyup change', function(e) {
$('#question'+q2ID+' td.rank').click(function (event) {
setTimeout(function() {
$('#question'+q2ID+' select.select').attr('disabled', true);
handleChecked();
$('#question'+q2ID+' td.item input.text').each(function(i) {
}, 250);
if ($(this).val() == '') {
$('#question'+q2ID+' select.select').attr('disabled', false);
}
});
});
});
}</syntaxhighlight>
function handleChecked() {
 
// Build an array of checked answers
'''FOR BOTH FILTER AND RANKING QUESTIONS ON SAME PAGE:'''
var checkedAnswers = [];
*Create a multiple options question and a ranking question on the same page (in the same group).
$('input.checkbox:checked', thisQuestion).each(function(i) {
*Both questions '''MUST''' have identical sub-questions and sub-question codes.
if($(this).closest('.answer-item').hasClass('other-item')) {
*Place the following script in the source of one of the questions.
checkedAnswers.push($(this).closest('.answer-item').find('input.text').val());
*Replace "MM" with the ID of the multiple options question and "RR" with the ID of the ranking question.
}
*The function will hide the ranking question unless at least two options are selected in the multiple options question, in which case, it will only show the options selected.<syntaxhighlight lang="javascript" enclose="div">
else {
<script type="text/javascript" charset="utf-8">
checkedAnswers.push($.trim($(this).nextAll('.label-text:eq(0)').text()));
 
}
$(document).ready(function() {
});
rankFilter(MM, RR);
});
// Load the hidden question with a random item from the array
var checkedLength = checkedAnswers.length;
$(hiddenInput).val(checkedAnswers[Math.floor(Math.random()*checkedLength)]);
// Fire ExpressionScript
checkconditions(hiddenInput.value, hiddenInput.name, hiddenInput.type);
}
    });
</script></syntaxhighlight>
</script></syntaxhighlight>


'''FOR FILTER AND RANKING QUESTIONS ON SEPARATE PAGES:'''
=Display secondary options in a "Multiple options" question=
*Create a multiple options question on page 1.
*Create a multiple options question and a ranking question on page 2.
*All three questions '''MUST''' have identical sub-questions and sub-question codes.
*Set the multiple options on page 2 to be filtered by the multiple options on page 1.
*Place the following script in the source of one of the questions on page 2.
*Replace "MM" with the ID of the multiple options question and "RR" with the ID of the ranking question. Do not modify the "1".
*The function will hide the multiple options question. If at least 2 options were selected in the multiple options on page 1, the corresponding options will be checked in the hidden multiple options question which will then be used to control the display of the ranking question.<syntaxhighlight lang="javascript" enclose="div">


<script type="text/javascript" charset="utf-8">
''Tested with: LimeSurvey versions 2.06, 2.5 and 3.x, IE 7-11, Firefox, Chrome''
$(document).ready(function() {
rankFilter(MM, RR, 1);
});
</script></syntaxhighlight>


=Filter "Array by Column" Question With "Multiple-Options"=
This workaround allows you to display secondary options in a "Multiple options" question if a primary option is checked as in the images below.


''Tested with: 2.05''
Primary option not selected:<br />
[[File:Secondary_options_1.png]]


This workaround applies a filter to an "Array by Column" question based on the checked boxes of a previous multiple-options question. There are two methods - one for filter and array questions being on the same page and a slightly different one for the filter question being on a page before the array question.


There is a [http://www.partnersinc.biz/surveys//index.php?sid=74213&newtest=Y&lang=en demo of both methods here].
Primary option selected:<br />
[[File:Secondary_options_2.png]]<br /><br />


Implementation is as follows.
'''DOWNLOADS:'''<br />
- [[Media:Demo_Secondary_Checkboxes.zip|Demo template for LS version 2.06]]<br />
- [[Media:Demo_Secondary_Checkboxes.lss|Demo survey for LS version 2.06]].<br />
- [[Media:Template_Demo_Secondary_Checkboxes_For_2.5.zip|Demo template for LS version 2.5]]<br />
- [[Media:Survey_Demo_Secondary_Multiple_Options_For_LS_25.zip|Demo survey for LS version 2.5]].<br /><br />


'''FOR BOTH METHODS:'''
==LimeSurvey version 2.06==
*[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
*If using LimeSurvey version 2.05 or newer, place the following functions in your template.js file. These will be accessed by either method.<syntaxhighlight lang="javascript" enclose="div">function filterArrByCol(qMultiOpt, qArray, prevPage) {


// If filter question is on a previous page, hide Q1 and check all "visible" boxes
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
if(prevPage == 1) {
#Create a "Multiple options" question including both the primary and secondary subquestions (in the order that you would like them displayed if all shown).
$('#question'+qMultiOpt+' li[id^="javatbd"]:visible input.checkbox').attr('checked', true);
#Add the following function to the end of template.js:<syntaxhighlight lang="javascript">
$('#question'+qMultiOpt+'').hide();
// A function to handle "secondary" checkboxes
}
function secondaryCheckboxes(qID, primaryPosition, secondaryCount) {
 
// Identify the elements
// Assign classes to the answer cells
var thisQuestion = $('#question'+qID);
$('#question'+qArray+' td.answer-item').each(function(i){
var primaryRow = $('li.question-item:eq('+(primaryPosition-1)+')', thisQuestion);
var classArr = $(this).attr('class').split('answer_cell_00');
var primaryInput = $('input.checkbox', primaryRow);
classArr = classArr[1].split(' ');
var secondaryRows = primaryRow.nextAll('li.question-item:lt('+(secondaryCount)+')');
var ansCode = classArr[0];
var secondaryInputs = $('input.checkbox', secondaryRows);
$(this).addClass('ans-'+ansCode+' filtered');
// Indent the secondaries
secondaryRows.css({ 'margin-left':'2.5em' });
// Initial states of the secondary answers
if (primaryInput.prop('checked') == false ) {
secondaryRows.hide();
}
// A listener on the primary answer to show or hide secondary answers
primaryInput.click(function (event) {  
// Hide/show the secondary answers accordingly
if (!$(this).is(':checked')) {
secondaryRows.hide();
secondaryInputs.prop('checked', false);
secondaryInputs.each(function(i) {
checkconditions(this.value, this.name, this.type);
});
}
else {
secondaryRows.show();  
}
});
});
 
}</syntaxhighlight>
// Assign classes to the answer label cells
#Call the function in the source of the question with the question ID (no editing necessary), the start position of the primary subquestion and the number of secondaries to follow. <br />So, in this example, subquestion 1 is a primary followed by 2 secondaries and subquestion 6 is a primary followed by 3 secondaries.<syntaxhighlight lang="javascript">
$('#question'+qArray+' table.subquestions-list tbody tr:eq(0) td.answer-item').each(function(i){
<script type="text/javascript" charset="utf-8">
var classArr2 = $(this).attr('class').split(' ans-');
$(document).ready(function() {
var ansCode2 = classArr2[1];
// Sub-question 1 is primary followed by 2 secondaries
$('#question'+qArray+' table.subquestions-list thead tr:eq(0) th:eq('+i+')').addClass('ans-'+ansCode2+'');
secondaryCheckboxes({QID}, 1, 2);
});
// Sub-question 6 is primary followed by 3 secondaries
 
secondaryCheckboxes({QID}, 6, 3);
// Fire the filter function on page load
    });
filterArr(qMultiOpt, qArray);
</script></syntaxhighlight>
 
// Listener on multi-opt checkboxes to fire the filter function
$('#question'+qMultiOpt+' input.checkbox').click(function(){
filterArr(qMultiOpt, qArray);
});
 
// On submit, clear all hidden radios of array
$('#movenextbtn, #movesubmitbtn').click(function(){
$('#question'+qArray+' td.filtered:hidden').each(function(i){
$('input.radio', this).prop('checked', false);
});
return true;
});
}


function filterArr(qMultiOpt, qArray) {
==LimeSurvey version 2.5==


if($('#question'+qMultiOpt+' input.checkbox:checked').length < 1) {
#Follow the first 2 implementation steps above
// Hide the array if no multi-opt options are checked
#Add the following function to the end of template.js:<syntaxhighlight lang="javascript">
$('#question'+qArray+'').hide();
// A function to handle "secondary" checkboxes
}
function secondaryCheckboxes(qID, primaryPosition, secondaryCount) {
else {
// Identify the elements
$('#question'+qArray+'').show();
var thisQuestion = $('#question'+qID);
 
$('div.question-item', thisQuestion).parent().addClass('answer-row');
// Hide all columns of array
var primaryRow = $('div.question-item:eq('+(primaryPosition-1)+')', thisQuestion).closest('.answer-row');
$('#question'+qArray+' table.subquestions-list tbody td.answer-item, #question'+qArray+' table.question thead th').hide();
var primaryInput = $('input.checkbox', primaryRow);
 
var secondaryRows = primaryRow.nextAll('div.answer-row:lt('+(secondaryCount)+')');
// Loop through multi-opt checkboxes and, if checked, show corresponding column of array
var secondaryInputs = $('input.checkbox', secondaryRows);
$('#question'+qMultiOpt+' input.checkbox').each(function(i){
if($(this).prop('checked') == true) {
// Indent the secondaries
var classArr3 = $(this).attr('id').split('X'+qMultiOpt);
secondaryRows.css({ 'margin-left':'2.5em' });
var ansCode3 = classArr3[1];
$('#question'+qArray+' .ans-'+ansCode3+'').show();
// Initial states of the secondary answers
}
if (primaryInput.prop('checked') == false ) {
});
secondaryRows.hide();
}
}
}</syntaxhighlight>
 
*If using LimeSurvey version 2.0 or older, place the following functions in your template.js file. These will be accessed by either method.<syntaxhighlight lang="javascript" enclose="div">
$(document).ready(function() {
 
function filterArrByCol(qMultiOpt, qArray, prevPage) {
// If filter question is on a previous page, hide Q1 and check all "visible" boxes
// A listener on the primary answer to show or hide secondary answers
if(prevPage == 1) {
primaryInput.click(function (event) {
$('#question'+qMultiOpt+' li[id^="javatbd"]:visible input.checkbox').attr('checked', true);
$('#question'+qMultiOpt+'').hide();
}
// Assign classes to the answer cells
// Hide/show the secondary answers accordingly
$('#question'+qArray+' table.question tbody td').each(function(i){
if (!$(this).is(':checked')) {
var classArr = $(this).attr('class').split('answer_cell_00');
secondaryRows.hide();
var ansCode = classArr[1];
secondaryInputs.prop('checked', false);
$(this).addClass('ans-'+ansCode+' filtered');
secondaryInputs.each(function(i) {
});
checkconditions(this.value, this.name, this.type);
// Assign classes to the answer label cells
$('#question'+qArray+' table.question tbody tr:eq(0) td').each(function(i){
var classArr2 = $(this).attr('class').split(' ans-');
var ansCode2 = classArr2[1];
$('#question'+qArray+' table.question thead tr:eq(0) th:eq('+i+')').addClass('ans-'+ansCode2+'');
});
// Fire the filter function on page load
filterArr(qMultiOpt, qArray);
 
// Listener on multi-opt checkboxes to fire the filter function
$('#question'+qMultiOpt+' input.checkbox').click(function(){
filterArr(qMultiOpt, qArray);
});
// On submit, clear all hidden radios of array
$('#movenextbtn, #movesubmitbtn').click(function(){
$('#question'+qArray+' td.filtered:hidden').each(function(i){
$('input.radio', this).attr('checked', false);
});
});
return true;
});
}
function filterArr(qMultiOpt, qArray) {
if($('#question'+qMultiOpt+' input.checkbox:checked').length < 1) {
// Hide the array if no multi-opt options are checked
$('#question'+qArray+'').hide();
}
}
else {
else {
$('#question'+qArray+'').show();
secondaryRows.show();  
// Hide all columns of array
$('#question'+qArray+' table.question tbody td, #question'+qArray+' table.question thead th').hide();
// Loop through multi-opt checkboxes and, if checked, show corresponding column of array
$('#question'+qMultiOpt+' input.checkbox').each(function(i){
if($(this).attr('checked') == true) {
var classArr3 = $(this).attr('id').split('X'+qMultiOpt);
var ansCode3 = classArr3[1];
$('#question'+qArray+' .ans-'+ansCode3+'').show();
}
});
}
}
}
});</syntaxhighlight>
'''FOR BOTH FILTER AND ARRAY QUESTIONS ON SAME PAGE:'''
*Create a multiple options question and an array-by-column question on the same page (in the same group).
*Both questions '''MUST''' have identical sub-questions and sub-question codes.
*Place the following script in the source of one of the questions.
*Replace "MM" with the ID of the multiple options question and "AA" with the ID of the ranking question.
*The function will hide the array question unless at least one option is selected in the multiple-options question, in which case, it will only show the corresponding array column(s).<syntaxhighlight lang="javascript" enclose="div">
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
filterArrByCol(MM, AA);
});
});
</script></syntaxhighlight>
}</syntaxhighlight>
 
#Call the function in the source of the question with the question ID (no editing necessary), the start position of the primary subquestion and the number of secondaries to follow. <br />So, in this example, subquestion 1 is a primary followed by 2 secondaries and subquestion 6 is a primary followed by 3 secondaries.<syntaxhighlight lang="javascript">
'''FOR FILTER AND ARRAY QUESTIONS ON SEPARATE PAGES:'''
<script type="text/javascript" charset="utf-8">
*Create a multiple options question on page 1.
*Create a multiple options question and an array-by-column question on page 2.
*All three questions '''MUST''' have identical sub-questions and sub-question codes.
*Set the multiple options on page 2 to be filtered by the multiple options on page 1.
*Place the following script in the source of one of the questions on page 2.
*Replace "MM" with the ID of the multiple options question and "AA" with the ID of the array question. Do not modify the "1".
*The function will hide the multiple options question. If options were selected in the multiple options on page 1, the corresponding options will be checked in the hidden multiple options question which will then be used to control the display of the array columns.<syntaxhighlight lang="javascript" enclose="div">
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
$(document).ready(function() {
filterArrByCol(MM, AA, 1);
// Sub-question 1 is primary followed by 2 secondaries
});
secondaryCheckboxes({QID}, 1, 2);
// Sub-question 6 is primary followed by 3 secondaries
secondaryCheckboxes({QID}, 6, 3);
    });
</script></syntaxhighlight>
</script></syntaxhighlight>


=Auto-tabbing between text input fields=
==LimeSurvey version 3.x==


''Tested with: 1.90+, IE 6/7, FireFox 3.0, Safari 3.2, Chrome 2.0''
#Follow the first 2 implementation steps above
 
#Add the following function to the end of custom.js:<syntaxhighlight lang="javascript">
This is a workaround to facilitate auto-tabbing between text input questions in a single group using a [http://plugins.jquery.com/project/autotab jQuery plugin by Mathachew] and the Maximum characters attribute of questions.
// A function to handle "secondary" checkboxes
 
function secondaryCheckboxes(qID, primaryPosition, secondaryCount) {
The behaviour will be: When the maximum number of characters has been entered into an input field, the focus (cursor) will be automatically moved to the next input field. When the backspace key is used to remove all characters from a field, the focus will be moved to the previous field. It's very useful in surveys having many text input questions to avoid having to manually tab or click between fields.
// Identify the elements
 
var thisQuestion = $('#question'+qID);
You can view a small demo survey [http://www.partnersinc.biz/surveys//index.php?sid=11327&newtest=Y&lang=en here].
var primaryRow = $('li.question-item:eq('+(primaryPosition-1)+')', thisQuestion).closest('li.question-item');
 
var primaryInput = $('input:checkbox', primaryRow);
Implementation is as follows:
var secondaryRows = primaryRow.nextAll('li.question-item:lt('+(secondaryCount)+')');
#Visit [http://plugins.jquery.com/autotabs http://plugins.jquery.com/autotabs], download the plugin script and save it as ''jquery.autotab-1.1b.js'' in the template folder.
var secondaryInputs = $('input:checkbox', secondaryRows);
#Link to the script by placing ''<script type="text/javascript" src="{TEMPLATEURL}jquery.autotab-1.1b.js"></script>'' within the ''<head>'' tag of startpage.pstpl.
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
// Indent the secondaries
#Create the text based questions that auto-tabbing is to be applied to. (All must be in the same group if you're using group by group presentation)
secondaryRows.css({ 'margin-left':'2.5em' });
#Define a Maximum characters attribute for all questions that the autotab is applied to.
{{QS:maximum_chars}}
// Initial states of the secondary answers
#In the source of the first question of the group that contains the auto-tabbed questions, add the following code.
if (primaryInput.prop('checked') == false ) {
secondaryRows.hide();
}
// A listener on the primary answer to show or hide secondary answers
primaryInput.on('change', function (event) {
// Hide/show the secondary answers accordingly
if (!$(this).is(':checked')) {
secondaryRows.hide();
secondaryInputs.prop('checked', false).trigger('change');
}
else {
secondaryRows.show();
}
});
}</syntaxhighlight>
#Call the function in the source of the question with the question ID (no editing necessary), the start position of the primary subquestion and the number of secondaries to follow. <br />So, in this example, subquestion 1 is a primary followed by 2 secondaries and subquestion 6 is a primary followed by 3 secondaries.<syntaxhighlight lang="javascript">
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
// Sub-question 1 is primary followed by 2 secondaries
secondaryCheckboxes({QID}, 1, 2);
// Sub-question 6 is primary followed by 3 secondaries
secondaryCheckboxes({QID}, 6, 3);
    });
</script></syntaxhighlight>


<syntaxhighlight lang="php" enclose="div"><script type="text/javascript" charset="utf-8">
=Minimum elapsed time before moving forward in survey=


  $(document).ready(function() {
''Tested with: 1.91, IE 7/8, Firefox 7.0, Safari 3.2''


      $('#answerSSSSSXGGXAA').focus(); //initially focus on the first input
This workaround interrupts the Next/Submit function and checks the amount of time elapsed since the page loaded. If the time is less than the minimum dictated, the respondent is alerted and not allowed to proceed. It is useful if you want to enforce a minimum amount of time spent viewing a page.
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
#Place the script below in the source of one of the questions on the page, replacing "TT" with the minimum number of seconds required before advancing is allowed.


      $('#answerSSSSSXGGXAA').autotab({ target: 'answerSSSSSXGGXBB' });
<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">


      $('#answerSSSSSXGGXBB').autotab({ previous: 'answerSSSSSXGGXAA', target: 'answerSSSSSXGGXCC' });
$(document).ready(function() {


      $('#answerSSSSSXGGXCC').autotab({ previous: 'answerSSSSSXGGXBB', target: 'answerSSSSSXGGXDD' });
minTime(TT);


      $('#answerSSSSSXGGXDD').autotab({ previous: 'answerSSSSSXGGXCC' });
function minTime(minTime) {


  });
var startTime = new Date();


</script></syntaxhighlight>
$('#movenextbtn, #movesubmitbtn').click(function(){


This example has 4 fields to be auto-tabbed between - all instances of the following must be replaced to be compatible with your survey (See image below):
var endTime = new Date();
*SSSSS -> Survey ID
*GG -> Group ID
*AA -> First question
*BB -> Second question
*CC -> Third question
*DD -> Last question


[[File:ids_343x252.gif]]
if((endTime - startTime)/1000 <= minTime) {
alert ('You must spend at least '+minTime+' seconds on the question.');
return false;
}
else {
return true;
}
});
}
});
</script>
</syntaxhighlight>


The ''target'' parameter defines where the next input is and the ''previous'' parameter defines (you guessed it) the previous input.
=Minimum number of characters in Long free text or Huge free text questions=


This plugin also has limited capability of formatting the input text - see comments in the plugin script.
''Tested with: 1.91, IE 7/8, Firefox 7.0, Safari 3.2''


=Custom response listeners=
<div class="simplebox">As of version 1.91, you can use regular expression masks (validation) to specify the minimum number of required characters.  However, even if the question is mandatory, it can still be blank.</div>


''Tested with: 1.85+ (7253), IE 6/7, FireFox 3.0, Safari 3.2, Chrome 2.0''
<div class="simplebox">As of version  1.92, if you make the question mandatory and use a validation mask to specify the minimum number of characters, then the user will be forced to answer the question.  Note that unlike 1.91, they can submit their current answers, even if partially invalid (so they are stored in the database), but they will not ber able to proceed to the next page until they have passed all mandatory and validity checks.</div>


Listeners can be used to perform functions when a change in an input is detected. There are many uses such as the [[Workarounds#Reverse_array_filter|Reverse array_filter]] but a simple one would be to warn a respondent if they enter a value that may be too high.
This workaround interrupts the Next/Submit function and checks the number of characters entered in a Long free text or Huge free text question. If the number is less than the minimum dictated, the respondent is alerted and not allowed to proceed.


We can use the [http://docs.jquery.com/Events/change jQuery change( ) event] to fire a function whenever there is a change to the input. The function will look for an input value higher than 10 and, if found, will warn the respondent.
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
#Place the script below in the source of one of the questions on the page where:
**11111 is the survey ID
**22 is the group ID
**33 is the question ID


<syntaxhighlight lang="javascript" enclose="div"><script type="text/javascript" charset="utf-8">
#Place the script below in the source of one of the questions on the page, replace "QQ" with the question ID and "CC" with the minimum number of characters required before advancing is allowed.


$(document).ready(function() {
<syntaxhighlight lang="php"><script type="text/javascript" charset="utf-8">
 
  $(document).ready(function() {
 
      minChar(QQ, CC);
 
      function minChar(qID, minChars) {
 
          $('#movenextbtn, #movesubmitbtn').click(function(){


$('#answer11111X22X33').change(function() {
              if($('#question'+qID+' textarea').val().replace(/ /g,'').length < minChars) {


if ( $('#answer11111X22X33').val() > 10 ) {
                  alert ('You must enter at least '+minChars+' characters.');
alert ("That's an awful lot. Are you sure?");
}
});
});
</script></syntaxhighlight>


=Text input masks=
                  return false;


''Tested with: 1.90+, IE 7, FireFox 3.0, Safari 3.2, Chrome 2.0''
              }


There may be instances where you would like to place a mask on a text input - for example only allowing respondents to enter the correct characters for a North American phone number ((###) ###-####) or a Canadian postal code (A1A 1A1) The [http://plugins.jquery.com/project/meioMask jQuery meioMask plugin by fabiomcosta] can be used to do this.
              else {


The plugin has several pre-defined masks and supports custom masks. It also allows for pasting of input, allows default values and can auto-focus the next form element when the current input is completely filled. See [http://www.meiocodigo.com/projects/meiomask/ http://www.meiocodigo.com/projects/meiomask/] for more details on features and options.
                  return true;


There is a small demo [http://www.partnersinc.biz/surveys//index.php?sid=36146&newtest=Y&lang=en here].
              }


Implementation for this demo was as follows:
          });
#Visit [http://www.meiocodigo.com/projects/meiomask/ http://www.meiocodigo.com/projects/meiomask/], download the latest plugin script and save it as jquery.meiomask.js in the template folder.
#Link to the script by placing <script type="text/javascript" src="{TEMPLATEURL}jquery.meiomask.js" charset="utf-8"></script> within the <head> tag of startpage.pstpl.
#Turn off $filterxsshtml to allow insertion of JavaScript in the questions. ([http://docs.limesurvey.org/tiki-index.php?page=Optional+settings&structure=English+Instructions+for+LimeSurvey#Filtering_dangerous_HTML_tags_in_survey_objects See documentation here])
#Create the text input questions that the masks are to be applied to
#In the source of the help section of the first question of the group that contains the masked inputs, add the following code. ([http://docs.limesurvey.org/tiki-index.php?page=Workarounds&structure=English+Instructions+for+LimeSurvey#How_to_use_Script_eg._JavaScript_etc._ See How to use script here])  


      }


<syntaxhighlight lang="javascript" enclose="div">
  });
<script type="text/javascript" charset="utf-8">


$(document).ready(function() {
</script></syntaxhighlight>
 
 
// Define custom masks
=Variable Length Array (Multi Flexible Text) question=
$.mask.masks = $.extend($.mask.masks,{
customMask1:{ mask: 'a9a 9a9' }, // Canadian postal code
// Maximum input of 9,999.99, reverse input, default value of 0.00
customMask2:{ mask: '99.999,9', type : 'reverse', defaultValue : '000' }
});
// Set the 'alt' attributes of the inputs
$('#answer111111X22XAA').attr('alt', 'phone-us'); // a pre-defined mask  
$('#answer111111X22XBB').attr('alt', 'customMask1'); // a custom mask  
$('#answer111111X22XCC').attr('alt', 'customMask2'); // a custom mask  
$('#answer111111X22XDD').attr('alt', 'cc'); // a pre-defined mask for credit card
// Tell the plugin to apply masks to these inputs
$('input:#answer111111X22XAA').setMask();
$('input:#answer111111X22XBB').setMask();
$('input:#answer111111X22XCC').setMask();
$('input:#answer111111X22XDD').setMask();
});
</script>
</syntaxhighlight>


''Tested with: 1.90+, IE 6/7/8, FireFox 3.0, Safari 3.2''


The first section defines two custom masks to be used - one for Canadian postal code with a format of A#A #A# and a second for a maximum amount of 9999.99 with reverse input (useful for cost inputs) and a default value of 0.00.
This workaround allows a respondent to add or remove rows of an Array (Multi Flexible Text) question. It's useful to avoid the page being cluttered with unnecessary array rows.


The next section sets the "alt" attributes for the text inputs. This will tell meioMask which mask we will be applying to each input. By default meioMask looks at the alt attribute to find which mask to apply however this can be changed using the [http://www.meiocodigo.com/projects/meiomask/#mm_options mask options].
A function is placed in the template.js file of your template (use template editor). This function hides all of the array rows except the first and inserts two elements that when clicked show or hide rows accordingly.


In the final section we apply the masks to the inputs.
It must be noted that you can not make this question Mandatory as the respondent will have no way to complete hidden fields. If you want the displayed fields to be mandatory you will need to validate them in another function or modify this function to only allow the addition of rows if all fields in the last row are completed and then disable those fields - that, however, is a little over the top for this example.


This example has 4 inputs with masks on them - all instances of the following must be replaced to be compatible with your survey (See image below):
Implementation is as follows:


*11111 -> Survey ID
#Set up your survey to use JavaScript (See instructions [[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|here]])
*22 -> Group ID
*AA -> First question
*BB -> Second question
*CC -> Third question
*DD -> Fourth question


[[File:Ids 343x252.gif]]
#Add the following script to your ''template.js'' file.


=Text input masks - second method=
#Replace "QQ" in the function call at the end with the question ID.


''Tested with: 1.91RC6+, FireFox 4.0''
#Create one or more Array (Multi Flexible Text) questions


There may be instances where you would like to place a mask on a text input - for example only allowing respondents to enter the correct characters for a North American phone number ((###) ###-####) or a US social security number (###-##-####). The jQuery meioMask plugin by fabiomcosta is no longer supported but an alternative exists. The DigitalBush Masked Input Plugin is updated and simple to implement in LimeSurvey. See [http://digitalbush.com/projects/masked-input-plugin/ http://digitalbush.com/projects/masked-input-plugin/] for more details on features and options.
#If you want to apply it to more arrays on the same page simply add more calls with the appropriate question IDs. (eg. varLengthArray(1); varLengthArray(2);...)


Implementation as follows:


#Visit [http://digitalbush.com/projects/masked-input-plugin/ http://digitalbush.com/projects/masked-input-plugin/], download the latest plugin script and save it as jquery.maskedinput.js in the template folder.
<syntaxhighlight lang="javascript">$(document).ready(function() {
#Link to the script by placing <script type="text/javascript" src="{TEMPLATEURL}jquery.maskedinput.js" charset="utf-8"></script> within the <head> tag of startpage.pstpl.
#Turn off $filterxsshtml to allow insertion of JavaScript in the questions. ([http://docs.limesurvey.org/tiki-index.php?page=Optional+settings&structure=English+Instructions+for+LimeSurvey#Filtering_dangerous_HTML_tags_in_survey_objects See documentation here])
#Create the text input questions that the masks are to be applied to
#In the source of the help section of the first question of the group that contains the masked inputs, add the following code. ([http://docs.limesurvey.org/tiki-index.php?page=Workarounds&structure=English+Instructions+for+LimeSurvey#How_to_use_Script_eg._JavaScript_etc._ See How to use script here])


  // A function to add or remove rows of an Array (Multi Flexible)(Text) question
function varLengthArray(qID) {


<syntaxhighlight lang="javascript" enclose="div">
if ($('#question'+qID+'').length > 0) {
<script type="text/javascript" charset="utf-8">
$(document).ready(function($) {
$("#answer55431X1X5").mask("999-99-9999",{ placeholder:"#" });
});
</script>
</syntaxhighlight>


// The HTML content of the Add/Remove elements - modify as you wish
var addContent = '[+]';
var removeContent = '[-]';


Replace the "#answer55431X1X5" with your survey ID, group ID, and question ID. You can change the mask format and change or eliminate the placeholder if you wish. If you have multiple questions in the same group, simply copy and paste the line that begins with "$" and change the code to what is needed for your application.
// Create the Add and Remove elements & insert them
var el1 = document.createElement('div');
el1.setAttribute('id','addButton'+qID);
document.body.appendChild(el1);
var el2 = document.createElement('div');
el2.setAttribute('id','removeButton'+qID);
document.body.appendChild(el2);


=Select a random response to a "Multiple options" question for later use=
// Move them to after the array
$( 'div#addButton'+qID ).appendTo($( '#question' + qID + ' table.question' ).parent());
$( 'div#removeButton'+qID ).appendTo($( '#question' + qID + ' table.question' ).parent());


''Tested with: 1.90+, IE 6/7, FireFox 3.0, Safari 3.2, Chrome 2.0''
// Insert their HTML
$( 'div#addButton'+qID ).html( addContent );
$( 'div#removeButton'+qID ).html( removeContent );


This workaround allows you to randomly select one of the checked boxes in a "Multiple options" question and store it's label for later use in the survey. A possible use would be to have a question asking what products were purchased and then randomly selecting one of the products to ask further questions about.
// Style the elements - you can modify here if you wish
$( 'div#addButton'+qID ).css({
'margin':'10px 0 0 10px',
'padding':'1px',
'text-align':'center',
'font-weight':'bold',
'width':'auto',
'cursor':'pointer',
'float':'left'
});


There is a small demo of the above example [http://www.partnersinc.biz/surveys//index.php?sid=81252&newtest=Y&lang=en here].
$( 'div#removeButton'+qID ).css({
'margin':'10px 0 0 10px',
'padding':'1px',
'text-align':'center',
'font-weight':'bold',
'width':'auto',
'cursor':'pointer',
'float':'left'
});


Basically what we've done here is to put listeners on all of the checkboxes. If a change of state is detected in any of them the "randomResponse" function is called which loops through all of the checkboxes and if it finds a checked state, that box's label is added to an array. Then a random item is pulled from the array and used to populate the hidden question. This hidden question can then be drawn upon later in the survey using [http://docs.limesurvey.org/tiki-index.php?page=Adding+a+Question#Information_from_previous_answers INSERTANS] or [http://docs.limesurvey.org/tiki-index.php?page=Setting+conditions&structure=English+Instructions+for+LimeSurvey Conditions].
// Initially hide the Remove element
$( 'div#removeButton'+qID ).hide();


Implementation for this demo is as follows:
// Call the functions below when clicked
$( 'div#addButton'+qID ).click(function (event) {
addRow(qID);
});
$( 'div#removeButton'+qID ).click(function (event) {
removeRow(qID);
});


#[http://docs.limesurvey.org/tiki-index.php?page=Workarounds%3A+Manipulating+a+survey+at+runtime+using+Javascript&structure=English+Instructions+for+LimeSurvey#How_to_use_Script_eg._JavaScript_etc._in_LimeSurvey_ Set up your survey to use JavaScript].
// Function to add a row, also shows the Remove element and hides the
#Create a "Multiple options" question that we're going to randomly pick a checked box from
//Add element if all rows are shown
#Create a text input question to store the label of the checked box in (we're going to hide this with JavaScript)
function addRow(qID) {
#Place the script below in the source of one of the questions on the page.  
var arrayRow = '#question' + qID + ' table.question tbody';
var rowCount = $( arrayRow ).size() - 1;
$( arrayRow + '[name="hidden"]:first' ).attr('name', 'visible').show();
$( 'div#removeButton'+qID ).show();
if ( $( arrayRow + ':eq(' + rowCount + ')' ).attr('name') == 'visible' ) {
$( 'div#addButton'+qID ).hide();
}
}


The following edits are required to implement this in your survey:
// Function to remove a row, also clears the contents of the removed row,
// shows the Add element if the last row is hidden and hides the Remove
// element if only the first row is shown
function removeRow(qID) {
var arrayRow = '#question' + qID + ' table.question tbody';
var rowCount = $( arrayRow ).size() - 1;
$( arrayRow + '[name="visible"]:last input[type="text"]' ).val('');
$( arrayRow + '[name="visible"]:last' ).attr('name', 'hidden').hide();
$( 'div#addButton'+qID ).show();
if ( $( arrayRow + ':eq(1)' ).attr('name') == 'hidden' )  {
$( 'div#removeButton'+qID ).hide();
}
}


*EDIT HERE (1) - 11111 -> Survey ID, 22 -> Group ID, 44 -> hidden question ID.
// Just some initialization stuff
*EDIT HERE (2) - 44 -> hidden question ID.
var arrayRow = '#question' + qID + ' table.question tbody';
*EDIT HERE (3) - 11111 -> Survey ID, 22 -> Group ID, 33 -> multiple option question ID, A/B/C/D/E/F -> multiple option answer codes. Also add or remove CBs for more or less checkboxes.
var rowCount = '';
*EDIT HERE (4) - add or remove CBs for more or less checkboxes.
*EDIT HERE (5) - add or remove CBs for more or less checkboxes.


For more info on these IDs see [http://docs.limesurvey.org/tiki-index.php?page=SGQA+identifier&structure=English+Instructions+for+LimeSurvey SGQA identifier].
// Initially hide all except first row or any rows with populated inputs
$( arrayRow ).each(function(i) {
if ( i > 0 ) {
// We also need to give the hidden rows a name cause IE doesn't
// recognize jQuery :visible selector consistently
$( this ).attr('name', 'hidden').hide();


 
$('input[type=text]', this).each(function(i) {
<syntaxhighlight lang="javascript" enclose="div">
if ($(this).attr('value') != '') {
<script type="text/javascript" charset="utf-8">
$(this).parents('tbody:eq(0)').attr('name', 'visible').show();
$( 'div#removeButton'+qID ).show();
$(document).ready(function() {
}
});
//// Identify the hidden question input
rowCount = i;
//// EDIT HERE (1) - replace with your hidden question input ID
var hiddenInput = '#answer11111X22X44';
//// Make the "hidden" question disappear
//// EDIT HERE (2) - replace with your hidden question ID
$('#question44').hide();
//// Identify the checkboxes to be randomized
//// EDIT HERE (3) - add or remove as required,
//// replace with your checkbox IDs
var CB1 = '#answer11111X22X33A';
var CB2 = '#answer11111X22X33B';
var CB3 = '#answer11111X22X33C';
var CB4 = '#answer11111X22X33D';
var CB5 = '#answer11111X22X33E';
var CB6 = '#answer11111X22X33F';
//// Create an array of all checkboxes to be randomized
//// EDIT HERE (4) - add or remove CBs as required
checkBoxes = new Array( CB1, CB2, CB3, CB4, CB5, CB6 );
var checkBoxesLength = checkBoxes.length;
//// The randomizing function - no editing required
function randomResponse () {
var boxLabels = new Array();
// loop through all of the checkboxes and if they are checked,
// add their label to the boxLabels array
for( var i=0; i<checkBoxesLength; i++ ) {
if ( $(checkBoxes[i]).attr('checked') == true ) {
var cbid = checkBoxes[i].replace('#','');
boxLabels.push( $( 'label[for=' + cbid + ']' ).html() );
}
}
}
});
// find a random index of the responses array
var randomNum = Math.floor(Math.random() * boxLabels.length);
// populate the hidden question input with the
// random input value
$(hiddenInput).val( boxLabels[randomNum] );  
}
}
}
//// The listeners that will fire the randomize function if a
//// change occurs in the checkboxes
//// EDIT HERE (5) - add or remove CBs as required
$(CB1).change(function() {
randomResponse ();
});
$(CB2).change(function() {
randomResponse ();
});
$(CB3).change(function() {
randomResponse ();
});
$(CB4).change(function() {
randomResponse ();
});
$(CB5).change(function() {
randomResponse ();
});
$(CB6).change(function() {
randomResponse ();
});
});
</script>
</syntaxhighlight>


=Display secondary options in a "Multiple options" question=
// Call the function with a question ID
varLengthArray(QQ);


''Tested with: 2.05''
});</syntaxhighlight>


This workaround allows you to display secondary options in a "Multiple options" question if a primary option is checked as in the images below.
''Tested with: Version 3.3.0+180209, Safari 11.0.3''


Primary option not selected:<br />
The following code is an update of the above, tested with the latest LS3.
[[File:Secondary_options_1.jpg]]


<syntaxhighlight lang="javascript">$(document).ready(function() {


Primary option selected:<br />
  // A function to add or remove rows of an Array (Multi Flexible)(Text) question
[[File:Secondary_options_2.jpg]]<br /><br />
function varLengthArray(qID) {
if ($('#question'+qID+'').length > 0) {
   
// The HTML content of the Add/Remove elements - modify as you wish
var addContent = '[+]';
var removeContent = '[-]';


'''DOWNLOADS:'''<br />
// Create the Add and Remove elements & insert them
- [[Media:Demo_Secondary_Checkboxes.zip|Demo template]]<br />
var el1 = document.createElement('div');
- [[Media:Demo_Secondary_Checkboxes.lss|Demo survey]].<br /><br />
el1.setAttribute('id','addButton'+qID);
document.body.appendChild(el1);
var el2 = document.createElement('div');
el2.setAttribute('id','removeButton'+qID);
document.body.appendChild(el2);


'''IMPLEMENTATION:'''
// Move them to after the array
$( 'div#addButton'+qID ).appendTo($( '#question' + qID + ' table.ls-answers' ).parent());
$( 'div#removeButton'+qID ).appendTo($( '#question' + qID + ' table.ls-answers' ).parent());


1) [[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
// Insert their HTML
$( 'div#addButton'+qID ).html( addContent );
$( 'div#removeButton'+qID ).html( removeContent );


2) Create a "Multiple options" question including both the primary and secondary sub-questions (in the order that you would like them displayed if all shown).
// Style the elements - you can modify here if you wish
$( 'div#addButton'+qID ).css({
'margin':'10px 0 0 10px',
'padding':'1px',
'text-align':'center',
'font-weight':'bold',
'width':'auto',
'cursor':'pointer',
'float':'left'
});


3) Add the following function to the end of template.js:<syntaxhighlight lang="javascript" enclose="div">
$( 'div#removeButton'+qID ).css({
// A function to handle "secondary" checkboxes
'margin':'10px 0 0 10px',
function secondaryCheckboxes(qID, primaryPosition, secondaryCount) {
'padding':'1px',
// Identify the elements
'text-align':'center',
var thisQuestion = $('#question'+qID);
'font-weight':'bold',
var primaryRow = $('li.question-item:eq('+(primaryPosition-1)+')', thisQuestion);
'width':'auto',
var primaryInput = $('input.checkbox', primaryRow);
'cursor':'pointer',
var secondaryRows = primaryRow.nextAll('li.question-item:lt('+(secondaryCount)+')');
'float':'left'
var secondaryInputs = $('input.checkbox', secondaryRows);
// Indent the secondaries
secondaryRows.css({ 'margin-left':'2.5em' });
// Initial states of the secondary answers
if (primaryInput.prop('checked') == false ) {
secondaryRows.hide();
}
// A listener on the primary answer to show or hide secondary answers
primaryInput.click(function (event) {
// Hide/show the secondary answers accordingly
if (!$(this).is(':checked')) {
secondaryRows.hide();
secondaryInputs.prop('checked', false);
secondaryInputs.each(function(i) {
checkconditions(this.value, this.name, this.type);
});
});
}
else {
secondaryRows.show();
}
});
}</syntaxhighlight>


<br />4) Call the function in the source of the question with the question ID (no editing necessary), the start position of the primary sub-question and the number of secondaries to follow.
// Initially hide the Remove element
So, in this example, sub-question 1 is a primary followed by 2 secondaries and subquestion 6 is a primary followed by 3 secondaries.<syntaxhighlight lang="javascript" enclose="div">
$( 'div#removeButton'+qID ).hide();
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
// Sub-question 1 is primary followed by 2 secondaries
secondaryCheckboxes({QID}, 1, 2);
// Sub-question 6 is primary followed by 3 secondaries
secondaryCheckboxes({QID}, 6, 3);
    });
</script></syntaxhighlight>


=Minimum number of required answers in an array (pre 1.92)=
// Call the functions below when clicked
 
$( 'div#addButton'+qID ).click(function (event) {
''Tested with: 1.90+, IE 6/7, FireFox 3.0, Safari 3.2,''
addRow(qID);
 
});
<div class="simplebox">As of version 1.92 all array style questions have the min_answers and max_answers options, so this script is obsolete.</div>
$( 'div#removeButton'+qID ).click(function (event) {
removeRow(qID);
});


This workaround allows you to specify a '''minimum number''' of answers required in an array question. It is an alternative to the "Mandatory" parameter which specifies that '''all''' answers are required. It could also be easily modified to specify a maximum number of answers.
// Function to add a row, also shows the Remove element and hides the
//Add element if all rows are shown
function addRow(qID) {
var arrayRow = '#question' + qID + ' table.ls-answers tr.subquestion-list';
var rowCount = $( arrayRow ).size() - 1;
$( arrayRow + '[name="hidden"]:first' ).attr('name', 'visible').show();
$( 'div#removeButton'+qID ).show();
if ( $( arrayRow + ':eq(' + rowCount + ')' ).attr('name') == 'visible' )  {
$( 'div#addButton'+qID ).hide();
}
}


We accomplish this by placing an onload function in the source of the array question. This function hides the Next/Submit button and displays a warning element until it detects a minimum number of responses. It then hides the warning and shows the Next/Submit button.
// Function to remove a row, also clears the contents of the removed row,
// shows the Add element if the last row is hidden and hides the Remove
// element if only the first row is shown
function removeRow(qID) {
var arrayRow = '#question' + qID + ' table.ls-answers tr.subquestion-list';
var rowCount = $( arrayRow ).size() - 1;
$( arrayRow + '[name="visible"]:last input[type="text"]' ).val('');
$( arrayRow + '[name="visible"]:last' ).attr('name', 'hidden').hide();
$( 'div#addButton'+qID ).show();
if ( $( arrayRow + ':eq(1)' ).attr('name') == 'hidden' )  {
$( 'div#removeButton'+qID ).hide();
}
}


There is a demo [http://www.partnersinc.biz/surveys//index.php?sid=97166&newtest=Y&lang=en here].
// Just some initialization stuff
var arrayRow = '#question' + qID + ' table.ls-answers tr.subquestion-list';
var rowCount = '';


Implementation is as follows:
// Initially hide all except first row or any rows with populated inputs
#Turn off ''$filterxsshtml'' to allow insertion of JavaScript in the questions. (See [[Optional settings#Security|documentation here]])
$( arrayRow ).each(function(i) {
#Set up the template to use custom onload functions. (See the [[Workarounds#Custom onload function|workaround here]])
if ( i > 0 ) {
#In the source of the array question add the following onload function. (See [[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|How to use script here]])
// We also need to give the hidden rows a name cause IE doesn't
#Modify the ''SETTINGS'' section of the code as required.
// recognize jQuery :visible selector consistently
$( this ).attr('name', 'hidden').hide();


<syntaxhighlight lang="javascript" enclose="div">
$('input[type=text]', this).each(function(i) {
<script type="text/javascript" charset="utf-8">
if ($(this).attr('value') != '') {
 
$(this).parents('tbody:eq(0)').attr('name', 'visible').show();
// Wait until the document is fully loaded
$( 'div#removeButton'+qID ).show();
$(document).ready(function() {
}
});
/********************** SETTINGS **********************/
rowCount = i;
// The text to appear in the warning element
var warningText = 'Please answer at least 4 to continue';
// The question ID
var questionId = 137;
// The minimum number of answers required
var minNumber = 4;
/******************************************************/
// Create the warning element and place it after the submit button
var el = document.createElement('div');
el.setAttribute('id','warning');
document.body.appendChild(el);
$( '#warning' ).css({
'border':'1px solid #333333',
'width':'200px',
'padding':'3px',
'color':'red'
});
$('#warning').html(warningText);
$('input[type="submit"]').after($('#warning'));
// Detect the initial number of checked answers & display Next/Submit button accordingly
var inputInitCount = 0;
$('#question' + questionId + ' input').each(function(i) {
if ($( this ).attr('checked') == true ) {
inputInitCount++;
}
});
if (inputInitCount > (minNumber - 1)) {
$('input[type="submit"]').show();
$('#warning').hide();
}
else {
$('#warning').show();
$('input[type="submit"]').hide();
}
// Listener to detect number of checked answers & display Next/Submit button accordingly
$('#question' + questionId + ' input.radio, #question'+questionId+' tbody[id^="javatbd"] td').click(function() {
var inputCount = 0;
$('#question' + questionId + ' input.radio').each(function(i) {
if ($( this ).attr('checked') == true ) {
inputCount++;
}
}
});
});
if (inputCount > (minNumber - 1)) {
}
$('input[type="submit"]').show();
}
$('#warning').hide();
}
else {
$('#warning').show();
$('input[type="submit"]').hide();
}
// The original functions of the click event
checkconditions(this.value, this.name, this.type);
});
});
</script></syntaxhighlight>


=Minimum elapsed time before moving forward in survey=
// Call the function with a question ID
varLengthArray(QQ);


''Tested with: 1.91, IE 7/8, Firefox 7.0, Safari 3.2''
});</syntaxhighlight>


This workaround interrupts the Next/Submit function and checks the amount of time elapsed since the page loaded. If the time is less than the minimum dictated, the respondent is alerted and not allowed to proceed. It is useful if you want to enforce a minimum amount of time spent viewing a page.
==LimeSurvey version 6.x==
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
#Place the script below in the source of one of the questions on the page, replacing "TT" with the minimum number of seconds required before advancing is allowed.


<syntaxhighlight lang="javascript" enclose="div"><script type="text/javascript" charset="utf-8">
This is an updated script for version 6.x which also handles sub-question relevance:


$(document).ready(function() {
<syntaxhighlight lang="javascript">
$(document).ready(function() {


minTime(TT);
  // A function to add or remove rows of an Array (Multi Flexible)(Text) question
function varLengthArray(qID) {
if ($('#question'+qID+'').length > 0) {
var thisQuestion = $('#question'+qID);
// The HTML content of the Add/Remove elements - modify as you wish
var addContent = '[+] Add row';
var removeContent = '[-] Remove row';
// The classes for the Add/Remove elements - modify as you wish
// See https://getbootstrap.com/docs/5.0/getting-started/introduction/
var addClasses = 'inserted-button add btn btn-success';
var removeClasses = 'inserted-button remove btn btn-danger';
// The styles for the Add/Remove elements - modify as you wish
// These could be placed in your custom.css file.
var addStyles = 'margin:10px 0 10px 10px; padding:1px 5px; text-align:center; width:auto; cursor:pointer; float:left;';
var removeStyles = 'margin:10px 0 10px 10px; padding:1px 5px; text-align:center; width:auto; cursor:pointer; float:left;';
// Insert the buttons
$( 'table.ls-answers', thisQuestion).after('<div class="button-wrapper">\
<button type="button" class="'+addClasses+'" style="'+addStyles+'">'+addContent+'</button>\
<button type="button" class="'+removeClasses+'" style="display: none; '+removeStyles+'">'+removeContent+'</button>\
</div>');


function minTime(minTime) {
// Listeners on the buttons
$('.inserted-button.add', thisQuestion).on('click', function (event) {
addRow();
});
$('.inserted-button.remove', thisQuestion).on('click', function (event) {
removeRow();
});
// Define the relevant rows
var relevantRows = $('tr.subquestion-list:not(.ls-irrelevant)', thisQuestion);


var startTime = new Date();
// Function to add a row, show the "Remove" element and hide the "Add" element if all rows are shown
function addRow() {
$('[data-visible="false"]:first', thisQuestion).attr('data-visible', 'true').show();
$('.inserted-button.remove', thisQuestion).show();
console.log($('[data-visible="true"]', thisQuestion).length+' == '+$(relevantRows).length - 1);
if ($('[data-visible="false"]', thisQuestion).length == 0)  {
$('.inserted-button.add', thisQuestion).hide();
}
$('.inserted-button.add', thisQuestion).blur();
}


$('#movenextbtn, #movesubmitbtn').click(function(){
// Function to remove a row, clear the contents of the removed row,
// show the "Add" element if the last row is hidden and hide the "Remove" element if only the first row is shown
function removeRow() {
$('[data-visible="true"]:last input:text', thisQuestion).val('').trigger('keyup');
$('[data-visible="true"]:last', thisQuestion).attr('data-visible', 'false').hide();
$('.inserted-button.add', thisQuestion).show();
if ($('[data-visible="true"]', thisQuestion).length == 0)  {
$('.inserted-button.remove', thisQuestion).hide();
}
$('.inserted-button.remove', thisQuestion).blur();
}


var endTime = new Date();
// Initially hide all except first row or any rows with populated inputs
$(relevantRows).slice(1).each(function(i) {
$( this ).attr('data-visible', 'false').hide();


if((endTime - startTime)/1000 <= minTime) {
$('input[type=text]', this).each(function(i) {
alert ('You must spend at least '+minTime+' seconds on the question.');
if ($.trim($(this).val()) != '') {
return false;
$(this).closest('tr').attr('data-visible', 'true').show();
}
$('.inserted-button.remove', thisQuestion).show();
else {
}
return true;
});
}
});
});
}
}
});
}
</script>
// Call the function with a question ID
varLengthArray(QQ);
});
</syntaxhighlight>
</syntaxhighlight>


=Minimum number of characters in Long free text or Huge free text questions=
=Expandable Array=


''Tested with: 1.91, IE 7/8, Firefox 7.0, Safari 3.2''
''Tested with: 1.90+, IE 6/7, FireFox 3.5, Safari 3.2''


<div class="simplebox">As of version 1.91, you can use regular expression masks (validation) to specify the minimum number of required characters. However, even if the question is mandatory, it can still be blank.</div>
This workaround allows you to present an array question incrementally. That is to say that initially only the first option (row) in the array is shown. When that option is answered the next is shown and so-on.


<div class="simplebox">As of version  1.92, if you make the question mandatory and use a validation mask to specify the minimum number of characters, then the user will be forced to answer the question. Note that unlike 1.91, they can submit their current answers, even if partially invalid (so they are stored in the database), but they will not ber able to proceed to the next page until they have passed all mandatory and validity checks.</div>
A JavaScript function is placed in the source of the array question. This function hides all of the array rows except the first and then displays them as options are answered.


This workaround interrupts the Next/Submit function and checks the number of characters entered in a Long free text or Huge free text question. If the number is less than the minimum dictated, the respondent is alerted and not allowed to proceed.
The workaround supports following question types:
*Array (flexible labels)
*Array (flexible labels) by column
*Array (Yes/No/Uncertain)
*Array (Increase, Same, Decrease)
*Array (Flexible Labels) dual scale
*Array (10 point choice)


#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
Implementation is as follows:
#Set up your survey to use JavaScript. (See [[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|documentation here]])
#Create one or more Array questions
#In the source of the first array question add the following script.
#Replace "QQ" in the function call at the end of the script with the question ID of the array you would like to manipulate.
#If you want to apply it to more arrays on the same page, simply add more calls with the appropriate question IDs. (eg. expandingArray(1); expandingArray(2);...)


#Place the script below in the source of one of the questions on the page, replace "QQ" with the question ID and "CC" with the minimum number of characters required before advancing is allowed.
<syntaxhighlight lang="javascript">
<script type="text/javascript">


<syntaxhighlight lang="php" enclose="div"><script type="text/javascript" charset="utf-8">
$(document).ready(function() {


  $(document).ready(function() {
// A function to show subsequent rows of an array as options are checked
function expandingArray(qID) {


      minChar(QQ, CC);
// Build an array of the question rows
var arrayRow = '#question' + qID + ' table.question tbody tr';


      function minChar(qID, minChars) {
// Initially hide all rows unless an input was previously checked
$( arrayRow ).each(function(i) {


          $('#movenextbtn, #movesubmitbtn').click(function(){
if ( $( arrayRow  + ':eq(' + i + ') input.radio:checked' ).length != 0 ) {
$(this).attr('name', 'clickedRow');
}
else {
$(this).attr('name', 'hidden').hide();
}
});


              if($('#question'+qID+' textarea').val().replace(/ /g,'').length < minChars) {
// Now show the first hidden row
addRow();


                  alert ('You must enter at least '+minChars+' characters.');
// Add another row when an option is checked for the first time
$( '#question' + qID + ' td.answer input.radio' ).click(function (event) {


                  return false;
if ($(this).parents('tr:eq(0)').attr('name') != 'clickedRow') {
addRow();
$(this).parents('tr:eq(0)').attr('name', 'clickedRow');
}


              }
// The original function of the click event
checkconditions(this.value, this.name, this.type);
});


              else {
// Add another row when an table cell is clicked for the first time
$( '#question' + qID + ' table.question tbody td' ).click(function (event) {


                  return true;
if ($(this).parents('tr:eq(0)').attr('name') != 'clickedRow') {
addRow();
$(this).parents('tr:eq(0)').attr('name', 'clickedRow');
}
});


              }
// Function to add a row
function addRow() {
$( arrayRow + '[name="hidden"]:first' ).attr('name', 'visible').show();
// Now, scroll down
$("html, body").animate({ scrollTop: $(document).height() }, 1000);
}
}


          });
// Call the function with a question ID
 
expandingArray(QQ);
      }
 
  });


});
</script></syntaxhighlight>
</script></syntaxhighlight>


=Variable Length Array (Multi Flexible Text) question=
=Partially Randomized Answers - Array questions=


''Tested with: 1.90+, IE 6/7/8, FireFox 3.0, Safari 3.2''
''Tested with: 2.05''
 
This workaround allows a respondent to add or remove rows of an Array (Multi Flexible Text) question. It's useful to avoid the page being cluttered with unnecessary array rows.


A function is placed in the template.js file of your template (use template editor). This function hides all of the array rows except the first and inserts two elements that when clicked show or hide rows accordingly.
This workaround allows the answers of Array questions to be randomized while always keeping one subquestion at the end of the list.


There is a demo [http://www.partnersinc.biz/surveys//index.php?sid=53132&newtest=Y&lang=en here].
After the LimeSurvey randomizing has occurred a function in the source of a question moves the subquestion with a defined code to the bottom of the array.
 
It must be noted that you can not make this question Mandatory as the respondent will have no way to complete hidden fields. If you want the displayed fields to be mandatory you will need to validate them in another function or modify this function to only allow the addition of rows if all fields in the last row are completed and then disable those fields - that, however, is a little over the top for this example.


Implementation is as follows:
Implementation is as follows:
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]]
#Create an Array type question.
#Set the "Random order" question setting to "Randomize on each page load".
#Place the following script in the source of the question.
#Modify the "fixedCode" value as required.
<br />
<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">
$(document).ready(function() {
// The subquestion code to place in the last position
var fixedCode = 'A1';
// Identify this question
var q1ID = {QID};
var thisQuestion = $('#question'+q1ID);
// Move the "fixed" row to the end
$('table.subquestion-list tbody', thisQuestion).append($('tr[id$="X'+q1ID+fixedCode+'"]'));
// Fix up the array row background colours
$('tr.answers-list', thisQuestion).each(function(i){
$(this).removeClass('array1 array2').addClass('array'+(2-(i%2)));
});
    });
</script></syntaxhighlight>


#Set up your survey to use JavaScript (See instructions [[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|here]])
'''Download sample survey:'''<br />
 
[[Media:Partially_randomized_array.lss|Partially_randomized_array.lss]]
#Add the following script to your ''template.js'' file.
 
#Replace "QQ" in the function call at the end with the question ID.
 
#Create one or more Array (Multi Flexible Text) questions
 
#If you want to apply it to more arrays on the same page simply add more calls with the appropriate question IDs. (eg. varLengthArray(1); varLengthArray(2);...)


=Partially Randomized Answers - Multiple Options & List (radio) questions=


<syntaxhighlight lang="javascript" enclose="div">$(document).ready(function() {
''Tested with: 2.05''


  // A function to add or remove rows of an Array (Multi Flexible)(Text) question
This workaround allows the answers of Multiple Options & List (radio) questions to be randomized while always keeping one answer at the end of the list. It's useful if you want to randomize, say, the first 4 of your answers but always have the fifth displayed last as in the image below:
function varLengthArray(qID) {


if ($('#question'+qID+'').length > 0) {
[[File:random_336x179gif.gif]]


// The HTML content of the Add/Remove elements - modify as you wish
After the LimeSurvey randomizing has occurred a function in the source of a question moves the answer with the highest answer code to the end of the list.
var addContent = '[+]';
var removeContent = '[-]';


// Create the Add and Remove elements & insert them
Some conditions on the use of this are:
var el1 = document.createElement('div');
*The "Show No answer" survey setting must be set to "No"
el1.setAttribute('id','addButton'+qID);
*You must use sequential numbers, starting at 1 as response codes. (See image below)
document.body.appendChild(el1);
var el2 = document.createElement('div');
el2.setAttribute('id','removeButton'+qID);
document.body.appendChild(el2);


// Move them to after the array
[[File:Partially_Randomized_Answers_1.png]]
$( 'div#addButton'+qID ).appendTo($( '#question' + qID + ' table.question' ).parent());
$( 'div#removeButton'+qID ).appendTo($( '#question' + qID + ' table.question' ).parent());


// Insert their HTML
Implementation is as follows:
$( 'div#addButton'+qID ).html( addContent );
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]]
$( 'div#removeButton'+qID ).html( removeContent );
#Create a Multiple Options or List (radio) question with sequential response codes.
#Set the "Random order" question setting to "Randomize on each page load".
#Place the following script in the source of the question
<br />
<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">
$(document).ready(function() {
// Identify this question
var qID = {QID};
// Find the number of answers
var ansCount = $('#question'+qID+' li.answer-item').length;
// Place the last answer created at the end of the list
var answer = $( 'input[id$="X'+qID+ansCount+'"]');
var answerItem = $(answer).closest('li');
var answersList = $(answer).closest('ul');
$(answersList).append(answerItem);
});
</script></syntaxhighlight>
 
'''Download sample survey:'''<br />
[[Media:Partially_Ranomized_Answers.lss|Partially_Ranomized_Answers.lss]]
 
=Partially Randomized Answers - Multiple Options & List (radio) questions (Enhanced)=


// Style the elements - you can modify here if you wish
''Tested with LimeSurvey versions 2.06, 2.73.0, 3.6.2''
$( 'div#addButton'+qID ).css({
'margin':'10px 0 0 10px',
'padding':'1px',
'text-align':'center',
'font-weight':'bold',
'width':'auto',
'cursor':'pointer',
'float':'left'
});


$( 'div#removeButton'+qID ).css({
This workaround allows the answers of Multiple Options & List (radio) questions to be randomized while always keeping a specified number of answers fixed at the end of the list. It's useful if you want to randomize, say, the first 4 of your answers but always have the fifth and sixth displayed last as in the image below:
'margin':'10px 0 0 10px',
'padding':'1px',
'text-align':'center',
'font-weight':'bold',
'width':'auto',
'cursor':'pointer',
'float':'left'
});


// Initially hide the Remove element
[[File:Random v2.50.png]]
$( 'div#removeButton'+qID ).hide();


// Call the functions below when clicked
After the LimeSurvey randomizing has occurred, a function in the source of a question moves the answers with the highest answer codes to the end of the list.
$( 'div#addButton'+qID ).click(function (event) {
addRow(qID);
});
$( 'div#removeButton'+qID ).click(function (event) {
removeRow(qID);
});


// Function to add a row, also shows the Remove element and hides the
Some conditions on the use of this are:
//Add element if all rows are shown
*The "Show No answer" survey setting must be set to "No"  
function addRow(qID) {
*You must use sequential numbers, starting at 1 as response codes. (See image below)
var arrayRow = '#question' + qID + ' table.question tbody';
var rowCount = $( arrayRow ).size() - 1;
$( arrayRow + '[name="hidden"]:first' ).attr('name', 'visible').show();
$( 'div#removeButton'+qID ).show();
if ( $( arrayRow + ':eq(' + rowCount + ')' ).attr('name') == 'visible' )  {
$( 'div#addButton'+qID ).hide();
}
}


// Function to remove a row, also clears the contents of the removed row,
[[File:Random answers v2.50.png]]
// shows the Add element if the last row is hidden and hides the Remove
// element if only the first row is shown
function removeRow(qID) {
var arrayRow = '#question' + qID + ' table.question tbody';
var rowCount = $( arrayRow ).size() - 1;
$( arrayRow + '[name="visible"]:last input[type="text"]' ).val('');
$( arrayRow + '[name="visible"]:last' ).attr('name', 'hidden').hide();
$( 'div#addButton'+qID ).show();
if ( $( arrayRow + ':eq(1)' ).attr('name') == 'hidden' )  {
$( 'div#removeButton'+qID ).hide();
}
}


// Just some initialization stuff
==LimeSurvey version 2.06==
var arrayRow = '#question' + qID + ' table.question tbody';
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]]
var rowCount = '';
#Create a Multiple Options or List (radio) question with sequential response codes.
#Set the "Random order" question setting to "Randomize on each page load".
#Place the following script in the source of the question.
#Modify the "fixedAnswers" and "otherFixed" variables as required.
<br />
<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">
$(document).ready(function() {
// The number of answers to be fixed at the end of the list
var fixedAnswers = 2;


// Initially hide all except first row or any rows with populated inputs
// Set this to "true" if you want "Other" to be fixed in the last position
$( arrayRow ).each(function(i) {
var otherFixed = false;
if ( i > 0 ) {
// We also need to give the hidden rows a name cause IE doesn't
// Identify this question
// recognize jQuery :visible selector consistently
var qID = {QID};
$( this ).attr('name', 'hidden').hide();
 
// Find the number of answers
$('input[type=text]', this).each(function(i) {
var ansCount = $('#question'+qID+' li.answer-item').length;
if ($(this).attr('value') != '') {
if($('#question'+qID+' input[type="text"]').length > 0) {
$(this).parents('tbody:eq(0)').attr('name', 'visible').show();
ansCount = ansCount -1
$( 'div#removeButton'+qID ).show();
}
});
rowCount = i;
}
});
}
}
}
 
// Place the last n answers created at the end of the list
// Call the function with a question ID
var fixedIndex = fixedAnswers - 1;
varLengthArray(QQ);
for (var i=0; i<fixedAnswers; i++) {
var answer = $( 'input[id$="X'+qID+(ansCount-fixedIndex)+'"]');
var answerItem = $(answer).closest('li');
var answersList = $(answer).closest('ul');
$(answersList).append(answerItem);
fixedIndex--;
}
// Handle "Other"
if(otherFixed == true && $('#question'+qID+' input[type="text"]').length > 0) {
var otherAnswer = $('#question'+qID+' input[type="text"]');
var otherAnswerItem = $(otherAnswer ).closest('li');
var otherAnswersList = $(otherAnswer ).closest('ul');
$(otherAnswersList).append(otherAnswerItem);
}
});
</script></syntaxhighlight>


});</syntaxhighlight>
<br /><br />
==LimeSurvey version 2.73.0:==
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]]
#Create a Multiple Options or List (radio) question with sequential response codes.
#Set the "Random order" question setting to "Randomize on each page load".
#Place the following script in the source of the question.
#Modify the "fixedAnswers" and "otherFixed" variables as required.
<br />
<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">
$(document).ready(function() {
// The number of answers to be fixed at the end of the list
var fixedAnswers = 2;


=Expandable Array=
// Set this to "true" if you want "Other" to be fixed in the last position
var otherFixed = false;
// Identify this question
var qID = {QID};
// Find the number of answers
var ansCount = $('#question'+qID+' .answer-item').length;
if($('#question'+qID+' input[type="text"]').length > 0) {
ansCount = ansCount -1
}
// Place the last n answers created at the end of the list
var fixedIndex = fixedAnswers - 1;
for (var i=0; i<fixedAnswers; i++) {
var answer = $('input[id^="answer"][id$="X'+qID+'s'+(ansCount-fixedIndex)+'"]');
var answerItem = $(answer).closest('.answer-item');
var answersList = $(answer).closest('.answers-list');
if($('#question'+qID).hasClass('multiple-opt')) {
answer = $('input[id^="answer"][id$="X'+qID+(ansCount-fixedIndex)+'"]');
answerItem = $(answer).closest('.answer-item').parent();
answersList = $(answer).closest('.subquestion-list');
}
$(answersList).append(answerItem);
fixedIndex--;
}
// Handle "Other"
if(otherFixed == true && $('#question'+qID+' input[type="text"]').length > 0) {
var otherAnswer = $('#question'+qID+' input[type="text"]');
var otherAnswerItem = $(otherAnswer ).closest('.answer-item');
var otherAnswersList = $(otherAnswer ).closest('.answers-list');
if($('#question'+qID).hasClass('multiple-opt')) {
otherAnswerItem = $(otherAnswer ).closest('.answer-item').parent();
otherAnswersList = $(otherAnswer ).closest('.subquestion-list');
}
$(otherAnswersList).append(otherAnswerItem);
}
});
</script></syntaxhighlight>


''Tested with: 1.90+, IE 6/7, FireFox 3.5, Safari 3.2''
<br /><br />
 
==LimeSurvey version 3.x:==
This workaround allows you to present an array question incrementally. That is to say that initially only the first option (row) in the array is shown. When that option is answered the next is shown and so-on.
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]]
#Create a Multiple Options or List (radio) question with sequential response codes.
#Set the "Random order" question setting to "Randomize on each page load".
#Place the following script in the source of the question.
#Modify the "fixedAnswers" and "otherFixed" variables as required.
<br />
<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">
$(document).on('ready pjax:scriptcomplete',function(){
// The number of answers to be fixed at the end of the list
var fixedAnswers = 2;
// Set this to "true" if you want "Other" to be fixed in the last position
var otherFixed = false;
// Identify this question
var qID = {QID};
// Find the number of answers
var ansCount = $('#question'+qID+' .answer-item').length;
if($('#question'+qID+' input[type="text"]').length > 0) {
ansCount = ansCount -1
}
console.log(ansCount);
// Place the last n answers created at the end of the list
var fixedIndex = fixedAnswers - 1;
for (var i=0; i<fixedAnswers; i++) {
var answer = $('input[id^="answer"][id$="X'+qID+(ansCount-fixedIndex)+'"]');
var answerItem = $(answer).closest('.answer-item');
var answersList = $(answer).closest('ul');
$(answersList).append(answerItem);
fixedIndex--;
}
// Handle "Other"
if(otherFixed == true && $('#question'+qID+' input[type="text"]').length > 0) {
var otherAnswer = $('#question'+qID+' input[type="text"]');
var otherAnswerItem = $(otherAnswer ).closest('.answer-item');
var otherAnswersList = $(otherAnswer ).closest('ul');
$(otherAnswersList).append(otherAnswerItem);
}
});
</script></syntaxhighlight>
 
=Partially Randomized Answers - List (dropdown) questions=


A JavaScript function is placed in the source of the array question. This function hides all of the array rows except the first and then displays them as options are answered.
''Tested with: 1.90+, IE 6/7, FireFox 3.0, Safari 3.2''
 
This workaround, similar to the one above, allows the answers of List (dropdown) questions to be randomized while always keeping one answer at the end of the list. It's useful if you want to randomize, say, the first 4 of your answers but always have the fifth displayed last.
 
A script is placed in the source of a question. After the randomizing has occurred this script moves the answer with the highest answer code to the end of the list.


There is a demo [http://www.partnersinc.biz/surveys//index.php?sid=94958&newtest=Y&lang=en here].
A condition is that you must use sequential numbers, starting at 1 as response codes. (See image below)


The workaround supports following question types:
[[File:random_700x154.png]]
*Array (flexible labels)
*Array (flexible labels) by column
*Array (Yes/No/Uncertain)
*Array (Increase, Same, Decrease)
*Array (Flexible Labels) dual scale
*Array (10 point choice)


Implementation is as follows:
Implementation is as follows:
#Set up your survey to use JavaScript. (See [[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|documentation here]])
#Turn off ''$filterxsshtml'' to allow insertion of JavaScript in the questions. (See [[Optional settings#Security|documentation here]])
#Create one or more Array questions
#Create one or more List (dropdown) questions with sequential response codes. Set the [[Advanced question settings#random order|random_order question attribute]] to 1.
#In the source of the first array question add the following script.
#In the source of the first question add the following onload function. (See [[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|How to use script here]])
#Replace "QQ" in the function call at the end of the script with the question ID of the array you would like to manipulate.
#Replace the ID question at the end of the function call.
#If you want to apply it to more arrays on the same page, simply add more calls with the appropriate question IDs. (eg. expandingArray(1); expandingArray(2);...)


<syntaxhighlight lang="javascript" enclose="div">
<syntaxhighlight lang="javascript">
<script type="text/javascript">
<script type="text/javascript" charset="utf-8">


$(document).ready(function() {
$(document).ready(function() {


// A function to show subsequent rows of an array as options are checked
// Function to allow randomization of all answers
function expandingArray(qID) {
//except the last one in List/dropdown questions
function partRandDD(qID) {


// Build an array of the question rows
// Find the last answer option
var arrayRow = '#question' + qID + ' table.question tbody tr';
var ansCount = $( 'div#question' + qID + ' select option' ).length - 1;


// Initially hide all rows unless an input was previously checked
// Place the last answer option at the end of the list
$( arrayRow ).each(function(i) {
$( 'div#question' + qID + ' select option[value="' + ansCount + '"]' )
.appendTo($( 'div#question' + qID + ' select' ));
}


if ( $( arrayRow  + ':eq(' + i + ') input.radio:checked' ).length != 0 ) {
// Call the function with the qID (replace the qID eg. partRandDD(244))  
$(this).attr('name', 'clickedRow');
partRandDD(qID);
}
//If you want to apply it to more questions on the same page simply add more calls with the appropriate IDs.
else {
partRandDD(qID);
$(this).attr('name', 'hidden').hide();
    });
}
</script></syntaxhighlight>
});


// Now show the first hidden row
=Partially Randomized Answers - Multiple numerical input=
addRow();


// Add another row when an option is checked for the first time
''Tested with: 1.90+, Safari 5.1''
$( '#question' + qID + ' td.answer input.radio' ).click(function (event) {


if ($(this).parents('tr:eq(0)').attr('name') != 'clickedRow') {
This workaround, similar to the ones above, allows the answers of Multiple numerical input questions to be randomized while always keeping one answer at the end of the list. It's useful if you want to randomize, say, the first 4 of your answers but always have the fifth displayed last.
addRow();
$(this).parents('tr:eq(0)').attr('name', 'clickedRow');
}


// The original function of the click event
An onload function is placed in the source of a question. After the randomizing has occurred this function moves the answer with the highest answer code to the end of the list.
checkconditions(this.value, this.name, this.type);
});


// Add another row when an table cell is clicked for the first time
A condition is that you must use sequential numbers, starting at 1 as response codes. (See image below)
$( '#question' + qID + ' table.question tbody td' ).click(function (event) {


if ($(this).parents('tr:eq(0)').attr('name') != 'clickedRow') {
[[File:random_700x154.png]]
addRow();
 
$(this).parents('tr:eq(0)').attr('name', 'clickedRow');
Implementation is as follows:
}
#Turn off ''$filterxsshtml'' to allow insertion of JavaScript in the questions. (See [[Optional settings#Security|documentation here]])
#Create one or more Multiple numerical input question with sequential response codes. Set the [[Advanced question settings#random order|random_order question attribute]] to 1.
#In the source of the first question add the following onload function. (See [[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|How to use script here]])
#Replace "SSSSS", "GG", "QQ" in the function call at the end with the survey ID, group ID and question ID respectively.
#If you want to apply it to more questions on the same page simply add more calls with the appropriate IDs. (eg. partRandDD(11111, 22, 1); partRandDD(11111, 22, 2); ...)
 
<syntaxhighlight lang="javascript">
<script type="text/javascript" charset="utf-8">
 
$(document).ready(function() {
 
// Function to allow randomization of all answers except the last one in multiple numeric input questions
function partRand(sID, gID, qID) {
 
// Find the number of answers
var ansCount = ''
$( 'div#question' + qID + ' div.answers li' ).each(function(i) {
ansCount = (i - 1);
});
});


// Function to add a row
// Place the last answer created at the end of the list - but before the first helping sum
function addRow() {
$( 'li#javatbd' + sID + 'X' + gID + 'X' + qID + ansCount + '' ).insertBefore($( 'div#question' + qID + ' div.answers li.multiplenumerichelp' ).first());
$( arrayRow + '[name="hidden"]:first' ).attr('name', 'visible').show();
 
}
}
}


// Call the function with a question ID
// Call the function with the SID, GID and QID
expandingArray(QQ);
partRand(SID, GID, QID);


});
});
</script></syntaxhighlight>
</script></syntaxhighlight>


=Partially Randomized Answers - Array questions=
Using this solution for Multiple numerical input questions might be a bit more complicated than for other question types. All answers are organized in an unordered list (<nowiki><ul></nowiki>) structure. However, if advanced question settings like ''Equals sum value'' are used, LimeSurvey adds helping information (e.g. sum of all entries and remainder) to the same unordered list using the class ''multiplenumerichelp'' for these additional list items. The above example  assumes that at least one of these list items is present and moves the last answer to the position right above the first helper item. Without such advanced settings, the code will most certainly need adaptation.
 
=Making one (or more) rows of an array mandatory=
 
{{Hint|Text=With Expression manager, you can use [[Expression_Manager_Examples#Using_em_validation_q_in_array|Using em_validation_q in array]] system.}}


''Tested with: 2.05''
''Tested with: 1.90+, IE 6/7, Firefox 3.0, Safari 3.2''


This workaround allows the answers of Array questions to be randomized while always keeping one sub-question at the end of the list.
This workaround allows you to define one or more rows of an array question as mandatory (instead of all of them which the "Mandatory" question setting does). It is [http://www.partnersinc.biz/surveys//index.php?sid=26842&newtest=Y&lang=en demonstrated here].


After the LimeSurvey randomizing has occurred a function in the source of a question moves the sub-question with a defined code to the bottom of the array.
A JavaScript function interrupts the submit function and checks if the inputs in the mandatory row(s) are populated. If not, an alert is popped up, the offending inputs are turned pink and the submit is aborted. Otherwise the submit goes ahead.


Implementation is as follows:
Implementation is as follows:
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]]
#Turn off ''$filterxsshtml'' to allow insertion of JavaScript in the questions (see [[Optional settings#Security|documentation here]]).
#Create an Array type question.
#Create the array question.
#Set the "Random order" question setting to "Randomize on each page load".
#In the source of the array question add the following script (see [[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|How to use script here]]).
#Place the following script in the source of the question.
#Modify the warningText (line10)as required.
#Modify the "fixedCode" value as required.
#Replace "QQ" (lines 13 and 14with the question ID and add calls as necessary for more rows - the example code renders rows 1 and 3 mandatory.
<br />
<syntaxhighlight lang="javascript" enclose="div"><script type="text/javascript" charset="utf-8">
$(document).ready(function() {
   
// The sub-question code to place in the last position
var fixedCode = 'A1';
// Identify this question
var q1ID = {QID};
var thisQuestion = $('#question'+q1ID);
// Move the "fixed" row to the end
$('table.subquestions-list tbody', thisQuestion).append($('tr[id$="X'+q1ID+fixedCode+'"]'));
// Fix up the array row background colours
$('tr.answers-list', thisQuestion).each(function(i){
$(this).removeClass('array1 array2').addClass('array'+(2-(i%2)));
});
    });
</script></syntaxhighlight>


'''Download sample survey:'''<br />
<syntaxhighlight lang="javascript">
[[Media:Partially_randomized_array.lss|Partially_randomized_array.lss]]
<script type="text/javascript" charset="utf-8">


=Partially Randomized Answers - Multiple Options & List (radio) questions=
$(document).ready(function () {


''Tested with: 2.05''
// Interrupt the submit function
$('form#limesurvey').submit(function () {


This workaround allows the answers of Multiple Options & List (radio) questions to be randomized while always keeping one answer at the end of the list. It's useful if you want to randomize, say, the first 4 of your answers but always have the fifth displayed last as in the image below:
// Override the built-in "disable navigation buttons" feature
$('#moveprevbtn, #movenextbtn, #movesubmitbtn').attr('disabled', '');


[[File:random_336x179gif.gif]]
var empty = 0;
var warningText = 'Please complete the highlighted inputs.';


After the LimeSurvey randomizing has occurred a function in the source of a question moves the answer with the highest answer code to the end of the list.
// Call the mandatory row function with question IDs and row numbers
mandatoryRow(QQ, 1);
mandatoryRow(QQ, 3);


Some conditions on the use of this are:
// A function to render rows of an array mandatory
*The "Show No answer" survey setting must be set to "No"
function mandatoryRow(qID, rowNum) {
*You must use sequential numbers, starting at 1 as response codes. (See image below)


[[File:Partially_Randomized_Answers_1.png]]
$('div#question' + qID + ' table.question tbody[id^="javatbd"]:eq(' + Number(rowNum - 1) + ') input[type="text"]').each(function (i) {


Implementation is as follows:
if ($(this).val() == '') {
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]]
$(this).css('background-color', 'pink');
#Create a Multiple Options or List (radio) question with sequential response codes.
empty = 1;
#Set the "Random order" question setting to "Randomize on each page load".
}
#Place the following script in the source of the question
else {
<br />
$(this).css('background-color', '#FFFFFF');
<syntaxhighlight lang="javascript" enclose="div"><script type="text/javascript" charset="utf-8">
}
});
$(document).ready(function() {
}
// Identify this question
var qID = {QID};
// Find the number of answers
var ansCount = $('#question'+qID+' li.answer-item').length;
// Place the last answer created at the end of the list
var answer = $( 'input[id$="X'+qID+ansCount+'"]');
var answerItem = $(answer).closest('li');
var answersList = $(answer).closest('ul');
$(answersList).append(answerItem);
});
</script></syntaxhighlight>


'''Download sample survey:'''<br />
if (empty == 1) {
[[Media:Partially_Ranomized_Answers.lss|Partially_Ranomized_Answers.lss]]
alert(warningText);
return false;
}
else {
return true;
}
});
 
});
 
</script></syntaxhighlight>
 
=Randomly displaying one of a few YouTube videos, and recording which was displayed=
 
''Tested in 1.85+''
 
Jason Cleeland has created and documented a method to randomly display one of a number of youtube videos in a single question, and save which one was displayed.
 
http://www.aptigence.com.au/home/archives/23-Random-display-of-videos-in-a-question-2-YouTube.html


=Partially Randomized Answers - Multiple Options & List (radio) questions (Enhanced)=
=Randomly displaying one of a few pictures, and recording which was displayed=


''Tested with: 2.05''
''Tested in 1.91+''


This workaround allows the answers of Multiple Options & List (radio) questions to be randomized while always keeping a specified number of answers fixed at the end of the list. It's useful if you want to randomize, say, the first 4 of your answers but always have the fifth and sixth displayed last as in the image below:
The following steps allow you to randomly display one of a few pictures and record in the dataset which picture was displayed. We use four pictures in this example, but you can easily adapt these scripts according to your needs.


[[File:Partially_Randomized_Answers_Enhanced_1.png]]
1. Create a new sub-directory '''pic''' in your LimeSurvey folder.


After the LimeSurvey randomizing has occurred a function in the source of a question moves the answer with the highest answer code to the end of the list.
2. Copy four pictures (named ''1.jpg'', ''2.jpg'', ''3.jpg'', and ''4.jpg'') in this pic sub-directory.


Some conditions on the use of this are:
3. Deactivate the ''Filter HTML for XSS'' function in LimeSurvey's ''Global Settings''.
*The "Show No answer" survey setting must be set to "No"
*You must use sequential numbers, starting at 1 as response codes. (See image below)


[[File:Partially_Randomized_Answers_Enhanced_2.png]]
4. Create a '''Short free text''' question where you want these pictures to be displayed.


Implementation is as follows:
5. Figure out the [[SGQA identifier|SGQA Identifier]] of this question (e.g., 12345X67X89).
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]]
#Create a Multiple Options or List (radio) question with sequential response codes.
#Set the "Random order" question setting to "Randomize on each page load".
#Place the following script in the source of the question.
#Modify the "fixedAnswers" variable as required.
<br />
<syntaxhighlight lang="javascript" enclose="div"><script type="text/javascript" charset="utf-8">
$(document).ready(function() {
// The number of answers to be fixed at the end of the list
var fixedAnswers = 2;
// Identify this question
var qID = {QID};
// Find the number of answers
var ansCount = $('#question'+qID+' li.answer-item').length;
// Place the last n answers created at the end of the list
var fixedIndex = fixedAnswers - 1;
for (var i=0; i<fixedAnswers; i++) {
var answer = $( 'input[id$="X'+qID+(ansCount-fixedIndex)+'"]');
var answerItem = $(answer).closest('li');
var answersList = $(answer).closest('ul');
$(answersList).append(answerItem);
fixedIndex--;
}
});
</script></syntaxhighlight>


'''Download sample survey:'''<br />
6. Paste the following code as question text after activating the editor's ''Source'' view and replace both SSSSSXGGXQQ strings with your SGQA identifier:
[[Media:Partially_Randomized_Answers_Enhanced.lss|Partially_Randomized_Answers_Enhanced.lss]]


=Partially Randomized Answers - List (dropdown) questions=
<syntaxhighlight lang="javascript">


''Tested with: 1.90+, IE 6/7, FireFox 3.0, Safari 3.2''
Please have a look at this picture:


This workaround, similar to the one above, allows the answers of List (dropdown) questions to be randomized while always keeping one answer at the end of the list. It's useful if you want to randomize, say, the first 4 of your answers but always have the fifth displayed last.
<br />


A script is placed in the source of a question. After the randomizing has occurred this script moves the answer with the highest answer code to the end of the list.
<script>


There is a demo [http://www.partnersinc.biz/surveys//index.php?sid=89344&newtest=Y&lang=en here].
$(document).ready(


A condition is that you must use sequential numbers, starting at 1 as response codes. (See image below)
function(){


[[File:random_700x154.png]]
// Find a random number (here between 1 and 4)


Implementation is as follows:
var randNumber = Math.floor(Math.random()*4 + 1);
#Turn off ''$filterxsshtml'' to allow insertion of JavaScript in the questions. (See [[Optional settings#Security|documentation here]])
#Create one or more List (dropdown) questions with sequential response codes. Set the [[Advanced question settings#random order|random_order question attribute]] to 1.
#In the source of the first question add the following onload function. (See [[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|How to use script here]])
#Replace "SSSSS", "GG", "QQ" in the function call at the end with the survey ID, group ID and question ID respectively.
#If you want to apply it to more questions on the same page simply add more calls with the appropriate IDs. (eg. partRandDD(11111, 22, 1); partRandDD(11111, 22, 2); ...)


<syntaxhighlight lang="javascript" enclose="div">
// Save the number as answer of this question
<script type="text/javascript" charset="utf-8">


$(document).ready(function() {
$('input#answerSSSSSXGGXQQ').val(randNumber);


// Function to allow randomization of all answers
// Hide this answer field
//except the last one in List/dropdown questions
 
function partRandDD(sID, gID, qID) {
document.getElementById("answerSSSSSXGGXQQ").style.display='none';


// Find the number of answers
// Show the picture
var ansCount = ''
$( 'div#question' + qID + ' select option' ).each(function(i) {
ansCount = i;
});


// Place the last answer created at the end of the list
picString = '<img src="pic/' + String(randNumber) + '.jpg">';
$( 'option[value="' + ansCount + '"]' ).appendTo($( 'div#question' + qID + ' select' ));
}


// Call the function with the SID, GID and QID
document.getElementById('pic').innerHTML = picString;
partRandDD(89344, 72, 244);


});
}


</script></syntaxhighlight>
);</script>


=Partially Randomized Answers - Multiple numerical input=
<p>


''Tested with: 1.90+, Safari 5.1''
    </p>


This workaround, similar to the ones above, allows the answers of Multiple numerical input questions to be randomized while always keeping one answer at the end of the list. It's useful if you want to randomize, say, the first 4 of your answers but always have the fifth displayed last.
<div id="pic">


An onload function is placed in the source of a question. After the randomizing has occurred this function moves the answer with the highest answer code to the end of the list.
    </div>


A condition is that you must use sequential numbers, starting at 1 as response codes. (See image below)
</syntaxhighlight>


[[File:random_700x154.png]]
If you want to display the number of this picture in a later question, use the following code as question text (don't forget to replace the SSSSSXGGXQQ string acordingly):


Implementation is as follows:
<syntaxhighlight lang="javascript">
#Turn off ''$filterxsshtml'' to allow insertion of JavaScript in the questions. (See [[Optional settings#Security|documentation here]])
#Create one or more Multiple numerical input question with sequential response codes. Set the [[Advanced question settings#random order|random_order question attribute]] to 1.
#In the source of the first question add the following onload function. (See [[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|How to use script here]])
#Replace "SSSSS", "GG", "QQ" in the function call at the end with the survey ID, group ID and question ID respectively.
#If you want to apply it to more questions on the same page simply add more calls with the appropriate IDs. (eg. partRandDD(11111, 22, 1); partRandDD(11111, 22, 2); ...)


<syntaxhighlight lang="javascript" enclose="div">
You saw picture number {INSERTANS:SSSSSXGGXQQ}.
<script type="text/javascript" charset="utf-8">


$(document).ready(function() {
</syntaxhighlight>


// Function to allow randomization of all answers except the last one in multiple numeric input questions
Perhaps you want to display the same picture again in a later question, but a little bit smaller (100px width, 100px height)? Just paste the following code as question text after activating the editor's ''Source'' view, and replace the SSSSSXGGXQQ string acordingly:
function partRand(sID, gID, qID) {


// Find the number of answers
<syntaxhighlight lang="javascript">
var ansCount = ''
$( 'div#question' + qID + ' div.answers li' ).each(function(i) {
ansCount = (i - 1);
});


// Place the last answer created at the end of the list - but before the first helping sum
Here you see the picture again: <br>
$( 'li#javatbd' + sID + 'X' + gID + 'X' + qID + ansCount + '' ).insertBefore($( 'div#question' + qID + ' div.answers li.multiplenumerichelp' ).first());


}
<script>


// Call the function with the SID, GID and QID
$(document).ready(
partRand(SID, GID, QID);


});
function(){


</script></syntaxhighlight>
// Get the picture number from previous question


Using this solution for Multiple numerical input questions might be a bit more complicated than for other question types. All answers are organized in an unordered list (<nowiki><ul></nowiki>) structure. However, if advanced question settings like ''Equals sum value'' are used, LimeSurvey adds helping information (e.g. sum of all entries and remainder) to the same unordered list using the class ''multiplenumerichelp'' for these additional list items. The above example  assumes that at least one of these list items is present and moves the last answer to the position right above the first helper item. Without such advanced settings, the code will most certainly need adaptation.
var randNumber = "{INSERTANS:SSSSSXGGXQQ}";


=Making one (or more) rows of an array mandatory=
// Show picture


''Tested with: 1.90+, IE 6/7, Firefox 3.0, Safari 3.2''
picString = '<img width="100" height="100" src="pic/' + String(randNumber) + '.jpg">';


This workaround allows you to define one or more rows of an array question as mandatory (instead of all of them which the "Mandatory" question setting does). It is [http://www.partnersinc.biz/surveys//index.php?sid=26842&newtest=Y&lang=en demonstrated here].
document.getElementById('pic').innerHTML = picString;


A JavaScript function interrupts the submit function and checks if the inputs in the mandatory row(s) are populated. If not, an alert is popped up, the offending inputs are turned pink and the submit is aborted. Otherwise the submit goes ahead.
}


Implementation is as follows:
);</script>
#Turn off ''$filterxsshtml'' to allow insertion of JavaScript in the questions (see [[Optional settings#Security|documentation here]]).
#Create the array question.
#In the source of the array question add the following script (see [[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|How to use script here]]).
#Modify the warningText (line10)as required.
#Replace "QQ" (lines 13 and 14)  with the question ID and add calls as necessary for more rows - the example code renders rows 1 and 3 mandatory.


<syntaxhighlight lang="javascript" enclose="div">
<div id="pic">
<script type="text/javascript" charset="utf-8">


$(document).ready(function () {
</div>


// Interrupt the submit function
</syntaxhighlight>
$('form#limesurvey').submit(function () {


// Override the built-in "disable navigation buttons" feature
=Multiple numerical input with max_num_value defined in token (personalized limit)=
$('#moveprevbtn, #movenextbtn, #movesubmitbtn').attr('disabled', '');


var empty = 0;
''Tested in 1.85+''
var warningText = 'Please complete the highlighted inputs.';


// Call the mandatory row function with question IDs and row numbers
<div class="simplebox">As of version 1.92, max_num_value can be an experession, so you can simply enter the {TOKEN} or other variable in that question attribute.</div>
mandatoryRow(QQ, 1);
mandatoryRow(QQ, 3);


// A function to render rows of an array mandatory
To make personalized survey with multiple numerical input when max_num_value (Maximum sum value) is different for each user and is loaded from the token use following tricks:
function mandatoryRow(qID, rowNum) {
*Create 1st group with numerical input field (1000X10X11). "Limit"
*Add java script into this question:


$('div#question' + qID + ' table.question tbody[id^="javatbd"]:eq(' + Number(rowNum - 1) + ') input[type="text"]').each(function (i) {
<syntaxhighlight lang="javascript">


if ($(this).val() == '') {
<script type="text/javascript" charset="utf-8">
$(this).css('background-color', 'pink');
empty = 1;
}
else {
$(this).css('background-color', '#FFFFFF');
}
});
}


if (empty == 1) {
function Custom_On_Load(){
alert(warningText);
return false;
}
else {
return true;
}
});


});
  document.getElementById('answer1000X10X11').value='{TOKEN:ATTRIBUTE_1}';


</script></syntaxhighlight>
  document.getElementById('answer1000X10X11').readOnly=1;


=Randomly displaying one of a few YouTube videos, and recording which was displayed=
}


''Tested in 1.85+''
</script>


Jason Cleeland has created and documented a method to randomly display one of a number of youtube videos in a single question, and save which one was displayed.
</syntaxhighlight>


http://www.aptigence.com.au/home/archives/23-Random-display-of-videos-in-a-question-2-YouTube.html
where:


=Randomly displaying one of a few pictures, and recording which was displayed=
TOKEN:ATTRIBUTE_1 is individual limit defined in token.


''Tested in 1.91+''
readOnly=1 // user shouldn't change it.
*Create 2nd group with multiple numerical input and add "Max value from SGQA" with 1000X10X11 as argument.


The following steps allow you to randomly display one of a few pictures and record in the dataset which picture was displayed. We use four pictures in this example, but you can easily adapt these scripts according to your needs.
Note: If you don't want to show first field with numerical input (and show definied limit in different way) you can add 2 lines to above script to hide whole question:


1. Create a new sub-directory '''pic''' in your LimeSurvey folder.
<syntaxhighlight lang="javascript">


2. Copy four pictures (named ''1.jpg'', ''2.jpg'', ''3.jpg'', and ''4.jpg'') in this pic sub-directory.
document.getElementById('question85').style.display='none';


3. Deactivate the ''Filter HTML for XSS'' function in LimeSurvey's ''Global Settings''.
document.getElementById('display85').value='';


4. Create a '''Short free text''' question where you want these pictures to be displayed.
</syntaxhighlight>


5. Figure out the [[SGQA identifier|SGQA Identifier]] of this question (e.g., 12345X67X89).
where:


6. Paste the following code as question text after activating the editor's ''Source'' view and replace both SSSSSXGGXQQ strings with your SGQA identifier:
question85 and display85 are field names.


<syntaxhighlight lang="javascript" enclose="div">
Note: It's no possible to create it into one group, because all SGQA references can only refer to data on a previous page because when clicking next the answer is stored at the DB and the SGQA is replaced by querying the database.


Please have a look at this picture:
=Default values in array questions=


<br />
''Tested with: 2.05+''


<script>
This workaround uses JavaScript to allow you to pre-check default answers in all subquestions of an array type question. You can select a column of the array to be checked as the page loads. The script first checks to see if a subquestion has already been answered and, if not, checks the default answer.


$(document).ready(
Implementation is as follows:
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use script]].
#Create the array question.
#In the source of the array question add the following script.
#Replace"QQ" with the question ID and "CC" with the column number to be checked.


function(){
<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">
$(document).ready(function() {
// A function to pre-check a column of an array
function checkedDefault(qID, column) {
var checkedCol = column - 1;
$('#question'+qID+' tr.subquestions-list').each(function(i) {
if ($('input.checkbox:checked', this).length == 0) {
$('input.checkbox:eq('+checkedCol+')', this).prop('checked', true);
$('input.checkbox:eq('+checkedCol+')', this).parent().find('input[type="hidden"]').val(1);
}
});
}
// Call the function with a question ID and column number
checkedDefault(QQ, CC);
});


// Find a random number (here between 1 and 4)
</script></syntaxhighlight>


var randNumber = Math.floor(Math.random()*4 + 1);
=Default Values In One Scale Of A Dual-scale Array Question=


// Save the number as answer of this question
''Tested with: 2.0+, IE 9/10, Firefox 24''


$('input#answerSSSSSXGGXQQ').val(randNumber);
'''Overview'''<br />
This workaround uses JavaScript to allow you to pre-check default answers in all subquestions of one scale of a dual-scale-array type question. You can select a scale and a column of that scale to be checked. The script first checks to see if a subquestion has already been answered and, if not, checks the default answer.


// Hide this answer field
'''Implementation:'''
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use script]].
#Create the array question.
#In the source of the array question add the following script.
#Adjust "''defaultAnsweredScale''" and "''defaultAnsweredColumn''" as necessary.


document.getElementById("answerSSSSSXGGXQQ").style.display='none';


// Show the picture
<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">
 
picString = '<img src="pic/' + String(randNumber) + '.jpg">';
$(document).ready(function() {
 
 
document.getElementById('pic').innerHTML = picString;
// Scale to set the default answer on
 
var defaultAnsweredScale = 2;
}
// Column of that scale to set as default answer
 
var defaultAnsweredColumn = 3;
);</script>
 
// Identify this question
<p>
var thisQuestion = $('#question{self.qid}');
 
    </p>
// Identify the scales and columns
 
$('.answer-item[class^="answer_cell_1"]', thisQuestion).addClass('scale-1-item');
<div id="pic">
$('.answer-item[class^="answer_cell_2"]', thisQuestion).addClass('scale-2-item');
$('tr.answers-list', thisQuestion).each(function(i) {
$('.scale-1-item', this).each(function(i) {
$(this).addClass('scale-1-column-'+(i+1)+'-item');
});
$('.scale-2-item', this).each(function(i) {
$(this).addClass('scale-2-column-'+(i+1)+'-item');
});
});
// Click the default answers
$('tr.answers-list', thisQuestion).each(function(i) {
if($('.scale-'+defaultAnsweredScale+'-item input[type="radio"]:checked', this).length == 0) {
$('.scale-'+defaultAnsweredScale+'-column-'+defaultAnsweredColumn+'-item input[type="radio"]:last', this).click();
}
});
});
</script></syntaxhighlight>


    </div>


</syntaxhighlight>
'''Download sample survey:'''<br />
[[Media:Dual_scale_with_default_answers.lss|Dual_scale_with_default_answers.lss]]


If you want to display the number of this picture in a later question, use the following code as question text (don't forget to replace the SSSSSXGGXQQ string acordingly):
=Record group view time=


<syntaxhighlight lang="javascript" enclose="div">
''Tested with: 1.87+ (8518), IE 6/7, Firefox 3.5, Safari 3.2''


You saw picture number {INSERTANS:SSSSSXGGXQQ}.
<div class="simplebox">As of version 1.92, group view time is accurately recorded in the timings table if you choose the Record Timings survey option.</div>


</syntaxhighlight>
This workaround uses JavaScript to populate a hidden question with the total time that a respondent views a group. If used in a group with only one question, it could be used to track the amount of time required to answer the question.


Perhaps you want to display the same picture again in a later question, but a little bit smaller (100px width, 100px height)? Just paste the following code as question text after activating the editor's ''Source'' view, and replace the SSSSSXGGXQQ string acordingly:
Implementation is as follows:
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
#In the group, create a short-text question. We will hide this later with JavaScript and populate it with the elapsed time.
#Place the following script in the source of the short-text question.
#Replace "QQ" in line 6 with the ID of the short-text question.


<syntaxhighlight lang="javascript" enclose="div">
The script populates the hidden question with the elapsed time since the group was opened. If a respondent leaves the group and then returns, the timer continues up from the elapsed time of the last visit.


Here you see the picture again: <br>
<syntaxhighlight lang="php">


<script>
<script type="text/javascript" charset="utf-8">


$(document).ready(
  $(document).ready(function(){


function(){
      // call the functions with the hidden timer question ID


// Get the picture number from previous question
      runTimer(QQ);


var randNumber = "{INSERTANS:SSSSSXGGXQQ}";
      function runTimer(timeQID) {


// Show picture
          $('#question'+timeQID).hide();


picString = '<img width="100" height="100" src="pic/' + String(randNumber) + '.jpg">';
          var initSec = '0';


document.getElementById('pic').innerHTML = picString;
          // Check for elapsed time from previous visits


}
          if ($('#question'+timeQID+' input.text').val()) {


);</script>
              var initTime = $('#question'+timeQID+' input.text').val();


<div id="pic">
              initTimeArr = initTime.split(':');


</div>
              initSec = Number[[initTimeArr[0]*3600]]+Number[[initTimeArr[1]*60]]+Number(initTimeArr[2]);


</syntaxhighlight>
          }


=Multiple numerical input with max_num_value defined in token (personalized limit)=
          // Run the timer update function


''Tested in 1.85+''
          recordTime(initSec, timeQID);


<div class="simplebox">As of version 1.92, max_num_value can be an experession, so you can simply enter the {TOKEN} or other variable in that question attribute.</div>
      }


To make personalized survey with multiple numerical input when max_num_value (Maximum sum value) is different for each user and is loaded from the token use following tricks:
  });
*Create 1st group with numerical input field (1000X10X11). "Limit"
*Add java script into this question:


<syntaxhighlight lang="javascript" enclose="div">
  // Timer update function


<script type="text/javascript" charset="utf-8">
  function recordTime(elapsedSec, timeQID){


function Custom_On_Load(){
      elapsedSec++;


  document.getElementById('answer1000X10X11').value='{TOKEN:ATTRIBUTE_1}';
      var h = Math.floor(elapsedSec / 3600);


  document.getElementById('answer1000X10X11').readOnly=1;
      var m = Math.floor(elapsedSec % 3600 / 60);


}
      var s = Math.floor(elapsedSec % 3600 % 60);


</script>
      var elapsedTime = [[h > 0 ? h + ":" : "00:") + (m > 0 ? (m < 10 ? "0" : "") + m + ":" : "00:") + (s < 10 ? "0" : "") + s);


</syntaxhighlight>
        $('#question'+timeQID+' input.text').val(elapsedTime);


where:
        // Run the timer update function every second


TOKEN:ATTRIBUTE_1 is individual limit defined in token.
        setTimeout('recordTime('+elapsedSec+', '+timeQID+')',1000);
 
    }
 
</script>


readOnly=1 // user shouldn't change it.
</syntaxhighlight>
*Create 2nd group with multiple numerical input and add "Max value from SGQA" with 1000X10X11 as argument.


Note: If you don't want to show first field with numerical input (and show definied limit in different way) you can add 2 lines to above script to hide whole question:
=Toggle visibility of groups=


<syntaxhighlight lang="javascript" enclose="div">
''Tested with: 1.90+, IE 6/7/8, Firefox 3.6, Safari 3.2''


document.getElementById('question85').style.display='none';
This workaround uses JavaScript to toggle the visibility of groups in an "all in one" survey. Initially the groups are hidden and there are links inserted to display the groups. It's useful to avoid the page being cluttered with many groups and lets the respondent focus on one at a time.


document.getElementById('display85').value='';
It's [http://www.partnersinc.biz/surveys//index.php?sid=33613&newtest=Y&lang=en demonstrated here].


</syntaxhighlight>
Implementation is as follows:


where:
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
#Set the survey to run in "all in one" mode.
#Add your groups and questions.
#Place the following script in the source of one of the questions in the first group.


question85 and display85 are field names.


Note: It's no possible to create it into one group, because all SGQA references can only refer to data on a previous page because when clicking next the answer is stored at the DB and the SGQA is replaced by querying the database.
The comments in the script are pretty self-explanatory but it does the following:


=Default values in array questions=
#initially hides all of the groups
#inserts a link above each one to toggle its display
#some styles are added to the clickable links but these could be done in template.css instead
#shows any groups that have an unanswered mandatory after a submit attempt


''Tested with: 2.05+''


This workaround uses JavaScript to allow you to pre-check default answers in all sub-questions of an array type question. You can select a column of the array to be checked as the page loads. The script first checks to see if a sub-question has already been answered and, if not, checks the default answer.
<syntaxhighlight lang="javascript">
<script type="text/javascript" charset="utf-8">


Implementation is as follows:
$(document).ready(function(){
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use script]].
#Create the array question.
#In the source of the array question add the following script.
#Replace"QQ" with the question ID and "CC" with the column number to be checked.


<syntaxhighlight lang="javascript" enclose="div"><script type="text/javascript" charset="utf-8">
// Insert show/hide divs before all group wrapper divs
$(document).ready(function() {
$('<div class="groupToggler"><span></span></div>').insertBefore('div[id^="group-"]');
// A function to pre-check a column of an array
function checkedDefault(qID, column) {
var checkedCol = column - 1;
$('#question'+qID+' tr.subquestions-list').each(function(i) {
if ($('input.checkbox:checked', this).length == 0) {
$('input.checkbox:eq('+checkedCol+')', this).prop('checked', true);
$('input.checkbox:eq('+checkedCol+')', this).parent().find('input[type="hidden"]').val(1);
}
});
}
// Call the function with a question ID and column number
checkedDefault(QQ, CC);
});
 
</script></syntaxhighlight>
 
=Default Values In One Scale Of A Dual-scale Array Question=
 
''Tested with: 2.0+, IE 9/10, Firefox 24''
 
'''Overview'''<br />
This workaround uses JavaScript to allow you to pre-check default answers in all sub-questions of one scale of a dual-scale-array type question. You can select a scale and a column of that scale to be checked. The script first checks to see if a sub-question has already been answered and, if not, checks the default answer.
 
'''Implementation:'''
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use script]].
#Create the array question.
#In the source of the array question add the following script.
#Adjust "''defaultAnsweredScale''" and "''defaultAnsweredColumn''" as necessary.
 
 
<syntaxhighlight lang="javascript" enclose="div"><script type="text/javascript" charset="utf-8">
$(document).ready(function() {
 
// Scale to set the default answer on
var defaultAnsweredScale = 2;
// Column of that scale to set as default answer
var defaultAnsweredColumn = 3;
// Identify this question
// Add some text to the show/hide divs
var thisQuestion = $('#question{self.qid}');
$('.groupToggler span').each(function(i) {
($( this ).text('Show/Hide Group '+(i+1)+''));
});
// Identify the scales and columns
// Add some styles to the show/hide divs
$('.answer-item[class^="answer_cell_1"]', thisQuestion).addClass('scale-1-item');
$('.groupToggler').css({
$('.answer-item[class^="answer_cell_2"]', thisQuestion).addClass('scale-2-item');
'padding':'5px 0 10px 0',
$('tr.answers-list', thisQuestion).each(function(i) {
'text-align':'center'
$('.scale-1-item', this).each(function(i) {
});
$(this).addClass('scale-1-column-'+(i+1)+'-item');
$('.groupToggler span').css({
});
'text-decoration':'underline',
$('.scale-2-item', this).each(function(i) {
'cursor':'pointer'
$(this).addClass('scale-2-column-'+(i+1)+'-item');
});
});
});
// Click the default answers
// Add some hover effects to the show/hide divs
$('tr.answers-list', thisQuestion).each(function(i) {
$(".groupToggler span").hover(
if($('.scale-'+defaultAnsweredScale+'-item input[type="radio"]:checked', this).length == 0) {
function () {
$('.scale-'+defaultAnsweredScale+'-column-'+defaultAnsweredColumn+'-item input[type="radio"]:last', this).click();
$(this).css({
'text-decoration':'none',
'font-weight':'bold'
});
},  
function () {
$(this).css({
'text-decoration':'underline',
'font-weight':'normal'
});
}
}
});
);
});
</script></syntaxhighlight>
// Initially hide all of the groups
 
// Toggle their visibility when the show/hide is clicked
 
$('.groupToggler span').click(function() {
'''Download sample survey:'''<br />
$(this).parent().next().toggle();
[[Media:Dual_scale_with_default_answers.lss|Dual_scale_with_default_answers.lss]]
return false;
}).parent().next().hide();
// Display any groups that have unanswered mandatories
$('.errormandatory').parents('div[id^="group-"]').show();
// Some tidying up
$('h1').next().next().hide();
$('.surveywelcome').css({
'margin-bottom':'10px'
});
$('.surveywelcome').next().hide();
$('.surveywelcome').next().next().hide();


=Record group view time=
});


''Tested with: 1.87+ (8518), IE 6/7, Firefox 3.5, Safari 3.2''
</script></syntaxhighlight>


<div class="simplebox">As of version 1.92, group view time is accurately recorded in the timings table if you choose the Record Timings survey option.</div>
=Use jQuery Autocomplete plugin to suggest answers for text inputs=


This workaround uses JavaScript to populate a hidden question with the total time that a respondent views a group. If used in a group with only one question, it could be used to track the amount of time required to answer the question.
== 1.91 and later versions==


Implementation is as follows:
''Tested with: 1.91+, IE 6/7/8, Firefox 3.6, Safari 3.2''
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
#In the group, create a short-text question. We will hide this later with JavaScript and populate it with the elapsed time.
#Place the following script in the source of the short-text question.
#Replace "QQ" in line 6 with the ID of the short-text question.


The script populates the hidden question with the elapsed time since the group was opened. If a respondent leaves the group and then returns, the timer continues up from the elapsed time of the last visit.
This workaround uses the [http://jqueryui.com/demos/autocomplete/ Autocomplete widget] included with jQuery UI to provide a respondent a list of suggested answers that the they can select from as they type in a text input.


<syntaxhighlight lang="php" enclose="div">
Although the plugin allows for much more complex applications, the two that I use most frequently are outlined below. The first method uses a relatively small list of suggestions that is stored in an array locally. The second is a little more complex - it uses a remote CSV file of suggestions that is accessed through AJAX an PHP.


<script type="text/javascript" charset="utf-8">
'''Both methods:'''
*Add the following styles to the end of your 'template.css' file


  $(document).ready(function(){
<syntaxhighlight lang="css">/* Autocomplete styles */


      // call the functions with the hidden timer question ID
ul.ui-autocomplete {
width: 250px !important;
padding: 5px 10px;
list-style: none;
}</syntaxhighlight>


      runTimer(QQ);
'''Method 1 (use local JavaScript array for data):'''
*Create a text input question
*Add the following script to the source of the text question
*Replace "QQ" with the ID of the text input question
*Replace the states with your list of suggestions (comma separated)


      function runTimer(timeQID) {
<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">


          $('#question'+timeQID).hide();
$(document).ready(function() {


          var initSec = '0';
var q1ID = 'QQ';


          // Check for elapsed time from previous visits
var states = "Alabama,Alaska,Arizona,Arkansas,California,Colorado,Connecticut,Delaware, District of Columbia,Florida,Georgia,Hawaii,Idaho,Illinois,Indiana,Iowa,Kansas,Kentucky, Louisiana,Maine,Montana,Nebraska,Nevada,New Hampshire,New Jersey,New Mexico,New York, North Carolina,North Dakota,Ohio,Oklahoma,Oregon,Maryland,Massachusetts,Michigan, Minnesota,Mississippi,Missouri,Pennsylvania,Rhode Island,South Carolina,South Dakota, Tennessee,Texas,Utah,Vermont,Virginia,Washington,West Virginia,Wisconsin,Wyoming".split(',');


          if ($('#question'+timeQID+' input.text').val()) {
$('#question'+q1ID+' input.text').autocomplete({
source: states
});


              var initTime = $('#question'+timeQID+' input.text').val();
});


              initTimeArr = initTime.split(':');
</script></syntaxhighlight>


              initSec = Number[[initTimeArr[0]*3600]]+Number[[initTimeArr[1]*60]]+Number(initTimeArr[2]);
'''Method 2 (use remote CSV file for data):'''
*Place the CSV file of suggestions in your template directory
*Place a PHP file containing the code below in your template directory
*Replace "countries2.csv" with your CSV filename (you could make this more universal by passing the filename in the URL)
*This file will grab the contents of the CSV and encode them in JSON format that the JavaScript can use
*Place the JavaScript code below in ths source of the text question
*Replace "QQ" with the ID of the text input question
*Replace "templates/yourTemplate/countries.php" with the path to the PHP file (from LimeSurvey root)
*This script will grab the jsonized data from the PHP file and load it into an array that the plugin can use


          }
PHP code:


          // Run the timer update function
<syntaxhighlight lang="php"><?php


          recordTime(initSec, timeQID);
$countriesArr = array();


      }
$file_handle = fopen("countries2.csv", "r");


  });
while (!feof($file_handle) ) {
$line_of_text = fgetcsv($file_handle);
array_push($countriesArr, $line_of_text[0]);
}


  // Timer update function
fclose($file_handle);


  function recordTime(elapsedSec, timeQID){
echo json_encode($countriesArr);


      elapsedSec++;
?></syntaxhighlight>


      var h = Math.floor(elapsedSec / 3600);
JavaScript code:


      var m = Math.floor(elapsedSec % 3600 / 60);
<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">


      var s = Math.floor(elapsedSec % 3600 % 60);
$(document).ready(function() {


      var elapsedTime = [[h > 0 ? h + ":" : "00:") + (m > 0 ? (m < 10 ? "0" : "") + m + ":" : "00:") + (s < 10 ? "0" : "") + s);
var qID = QQ;
var url = "templates/yourTemplate/countries.php";
var dataArr = [];


        $('#question'+timeQID+' input.text').val(elapsedTime);
$.getJSON(url,function(data){


        // Run the timer update function every second
$.each(data,function(i, item){
dataArr.push(item);
});


        setTimeout('recordTime('+elapsedSec+', '+timeQID+')',1000);
$('#question'+qID+' input.text').autocomplete({
source: dataArr
});


    }
});


</script>
});


</syntaxhighlight>
</script></syntaxhighlight>


=Toggle visibility of groups=
<br />
== Update for LimeSurvey 2.05+ ==
''Tested with: 2.05+, Chrome 40, FireFox 36, Opera 27, IE 11''


''Tested with: 1.90+, IE 6/7/8, Firefox 3.6, Safari 3.2''
'''With LimeSurvey 2.05+ it is NOT necessary to:'''
<ol><li>Download the jQuery Autocomplete widget</li>
<li>Add special autocomplete styling to the survey template (tested with the Skeletonquest template by Denis Chenu)</li>
</ol>


This workaround uses JavaScript to toggle the visibility of groups in an "all in one" survey. Initially the groups are hidden and there are links inserted to display the groups. It's useful to avoid the page being cluttered with many groups and lets the respondent focus on one at a time.
This workaround builds on the previous examples, but is updated for use with newer versions of LimeSurvey. Note that the [http://jqueryui.com/demos/autocomplete/ Autocomplete widget] is already included in LimeSurvey, as part of jQuery UI.


It's [http://www.partnersinc.biz/surveys//index.php?sid=33613&newtest=Y&lang=en demonstrated here].
<br />
Autocomplete may be configured to use different types of data input, three of which will be demonstrated in the following: Plain text, CSV-files, and MySQL databases.


Implementation is as follows:
<br />
 
'''1. Plain text input (the easiest, suitable for small lists or arrays).'''
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
#Set the survey to run in "all in one" mode.
#Add your groups and questions.
#Place the following script in the source of one of the questions in the first group.


To enable autocomplete of a list in plain text, simply add the following code to your question source. You will have to change qID to the question ID of the question you want the autocomplete to run in (this does not have to be the one you add the script to). In this case, the list has been added to the source directly, but you may also call the list as a predefined variable (see [http://jqueryui.com/demos/autocomplete/ jQuery website] for example):


The comments in the script are pretty self-explanatory but it does the following:
<syntaxhighlight lang="javascript">
 
<script type="text/javascript" charset="utf-8">
#initially hides all of the groups
#inserts a link above each one to toggle its display
    $(document).ready(function() {
#some styles are added to the clickable links but these could be done in template.css instead
#shows any groups that have an unanswered mandatory after a submit attempt
var qID = 1;
$('#question'+qID+' input[type="text"]').autocomplete({
minLength: 2, // This line is optional, signifies number of keystrokes before autocomplete is initiated
source: ["Test1","Test2","Test3"]
});
});
</script>
</syntaxhighlight>
 
<br />
'''2. Input from CSV file (a little trickier, requires several files. Suitable for larger lists and arrays).'''
 
Please note that, with this method, all processing happens browser-side. Thus, the entire contents of your CSV file will be exposed, no matter where you "hide" it (it is not possible to request files from outside the webroot on your server using javascript). If any of this concerns you, please proceed to the next paragraph, which deals with autocomplete based on database queries. To enable autocomplete based on output from a CSV file, you will need the following:
<ol><li>A comma-separated data file (CSV)</li>
<li>The [https://code.google.com/p/jquery-csv/ jquery.csv.js plugin] (v0.71)</li>
</ol>
 
First, download the jquery.csv.js script, and place it in the template folder (may require manual FTP, rather than upload through the template manager). You will need to call this script in the head section of the survey page, which you find in startpage.pstpl. Make sure you call it <strong>before</strong> {TEMPLATEJS}.
 
<syntaxhighlight lang="html5">
<script type="text/javascript" src="{TEMPLATEURL}jquery.csv.js"></script>
</syntaxhighlight>
 
<br />
To make the CSV file, you may use Microsoft Excel and "Save as CSV". Note that Excel produces CSV files where the columns are separated by a <strong>semicolon</strong>, not a comma. To make this work with the jquery.csv.js plugin, you will have to edit the jquery.csv.js file on line 43:
 
<syntaxhighlight lang="javascript">
41  $.csv = {
42    defaults: {
43      separator:',',  // Change this to: separator:';',
44      delimiter:'"',
45      headers:true
46    },
</syntaxhighlight>


<br />
Once the CSV file is in place, add the following javascript to your question source, using the correct question ID (qID) and url/filename for your CSV file:


<syntaxhighlight lang="javascript" enclose="div">
<syntaxhighlight lang="javascript">
<script type="text/javascript" charset="utf-8">
<script type="text/javascript" charset="utf-8">


$(document).ready(function(){
        $(document).ready(function() {
 
                var qID = 1;
 
var surveyRoot = location.pathname.split('index.php')[0];
                var url = surveyRoot+'datafile.csv';
 
                // Create an array to hold the data
                var testArr = new Array();
 
                // Grab the CSV contents
                $.get(url,function(data){
                        // Convert CSV contents to an array of arrays
                        fullArray = $.csv.toArrays(data);
 
                        // Load the data array
                        $(fullArray).each(function(i, item){
                                testArr.push(item[0]);
});
 
 
                        // Initialise the autocomplete plugin
                        $('#question'+qID+' input.text').autocomplete({
                                source: testArr
});
// console.log(data); This would be a gould place to put your debugging script. Remember to comment out or remove before going into production, to economize on server workload
                });
        });
 
</script>
</syntaxhighlight>
 
<br />
Now, the autocomplete should work. If not, see if your CSV file shows up under "Network" or "Resources" in your browser's inspector, and if it provides any output. Adding <strong>console.log(data);</strong> to your javascript (as indicated) should output the data to the browser console (you'll see the contents of your CSV file), and may be useful for debugging. Note that only the data from the current function (the function, within which the text was placed) will be presented.
 
<br />
'''3. Input from a MySQL database (trickiest, but also the most versatile and secure).'''
<br />This method requires the following:
<ol><li>Access to a MySQL database (tested with MySQL 5.5)</li>
<li>A PHP script to connect to and query the database</li>
</ol>
 
First, paste the following script in your question source code, replacing for the correct question ID (qID) and the url/filename of your master PHP file (which in this case is autocomplete.php, see below):
<syntaxhighlight lang="javascript">
<script type="text/javascript" charset="utf-8">


// Insert show/hide divs before all group wrapper divs
    $(document).ready(function() {
$('<div class="groupToggler"><span></span></div>').insertBefore('div[id^="group-"]');
var qID = 1;
// Add some text to the show/hide divs
$('#question'+qID+' input[type="text"]').autocomplete({
$('.groupToggler span').each(function(i) {
minLength: 2,  // This line is optional, signifies number of keystrokes before autocomplete is initiated
($( this ).text('Show/Hide Group '+(i+1)+''));
source: 'autocomplete.php'
});
// Add some styles to the show/hide divs
$('.groupToggler').css({
'padding':'5px 0 10px 0',
'text-align':'center'
});
$('.groupToggler span').css({
'text-decoration':'underline',
'cursor':'pointer'
});
});
// Add some hover effects to the show/hide divs
$(".groupToggler span").hover(
function () {
$(this).css({
'text-decoration':'none',
'font-weight':'bold'
});
},
function () {
$(this).css({
'text-decoration':'underline',
'font-weight':'normal'
});
}
);
// Initially hide all of the groups
// Toggle their visibility when the show/hide is clicked
$('.groupToggler span').click(function() {
$(this).parent().next().toggle();
return false;
}).parent().next().hide();
// Display any groups that have unanswered mandatories
$('.errormandatory').parents('div[id^="group-"]').show();
// Some tidying up
$('h1').next().next().hide();
$('.surveywelcome').css({'margin-bottom':'10px'});
$('.surveywelcome').next().hide();
$('.surveywelcome').next().next().hide();


});
});
</script>
</syntaxhighlight>
<br />
The structure of the database shouldn't matter too much, as long as you are using text fields. This example uses the UTF-8 character set. It is often a good idea to keep your login credentials in a separate file that you put outside of your webroot. In this case, I've put them in a file called constant.php:


</script></syntaxhighlight>
<syntaxhighlight lang="php">
<?php
// Login credentials
define('DB_SERVER', "localhost");
define('DB_USER', "username");
define('DB_PASSWORD', "password");
define('DB_DATABASE', "database_name");
define('DB_DRIVER', "mysql");
?>
</syntaxhighlight>


=Use jQuery Autocomplete plugin to suggest answers for text inputs=
<br />
The next file, database.php, deals with logging in to the database and performing the query that was requested through the javascript pasted above. Note that we're using the PDO convention rather than mysql_*, which has been deprecated for safety reasons (MySQLi remains a viable alternative, but is not shown here):


== 1.90 and previous versions==
<syntaxhighlight lang="php">
<?php


''Tested with: 1.90+, IE 6/7/8, Firefox 3.6, Safari 3.2''
// Connect and query MySQL database, using PDO
try {
  $db = new PDO(DB_DRIVER . ":dbname=" . DB_DATABASE . ";host=" . DB_SERVER . ";charset=utf8", DB_USER, DB_PASSWORD);
}
catch(PDOException $e) {
    echo $e->getMessage();
}
$return_arr = array();
if ($db)
{
$ac_term = "%".$_GET['term']."%";
$query = "SELECT * FROM table_name WHERE column_name LIKE :term";
$result = $db->prepare($query);
$result->bindValue(":term",$ac_term);
$result->execute();
// Fetch data and store in array
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
$row_array['label'] = $row['column_name'];
$row_array['value'] = $row['column_name'];
        array_push($return_arr,$row_array);
    }
}
</syntaxhighlight>


This workaround uses the [http://docs.jquery.com/Plugins/Autocomplete jQuery Autocomplete] plugin to provide a respondent a list of suggested answers that the they can select from as they type in a text input.
<br />
Finally, a PHP file (autocomplete.php) to include the previous two files and complete the query. This is the file that is called by the javascript, and the only one placed in the webroot:


Although the plugin allows for much more complex applications, the two that I use most frequently are outlined below. The first method uses a relatively small list of suggestions that is stored in an array locally. The second is a little more complex - it uses a remote CSV file of suggestions that is accessed through AJAX an PHP.
<syntaxhighlight lang="php">
<?php


I've put together a small demonstration [http://www.partnersinc.biz/surveys//index.php?sid=78611&newtest=Y&lang=en here] and implementation is as follows.
// Include files outside of webroot
set_include_path('/var/www/surveys/test-survey');


'''Both methods:'''
// Fetch database credentials
*[http://docs.jquery.com/Plugins/Autocomplete Download the plugin]
require_once ('constant.php');
*Place the files ''jquery.autocomplete.css'' and ''jquery.autocomplete.js'' in your template directory
*Add the following code inside the <head> element of startpage.pstpl


<syntaxhighlight lang="php" enclose="div"><script type="text/javascript" src="{TEMPLATEURL}jquery.autocomplete.js"></script>
// Connect to database and perform query
<link rel="stylesheet" type="text/css" href="{TEMPLATEURL}jquery.autocomplete.css" /></syntaxhighlight>
require_once ('database.php');


'''Method 1 ("States" question in the demo):'''
// Clear query
*Create a text input question
$db = null; 
*Add the following script to the source of the text question
*Replace "QQ" with the ID of the text input question
// Encode results in JSON. This is required for jquery autocomplete
*Replace the states with your list of suggestions (comma separated)
echo json_encode($return_arr);


<syntaxhighlight lang="javascript" enclose="div"><script type="text/javascript" charset="utf-8">
?>
</syntaxhighlight>


$(document).ready(function() {
<br />
This should complete your setup, but there are obviously a variety of pitfalls, at which point the previously outlined debugging techniques may be of help.
<br />


var q1ID = QQ;
== Use autocomplete to populate several fields==


var states = "Alabama,Alaska,Arizona,Arkansas,California,Colorado,Connecticut,Delaware, District of Columbia,Florida,Georgia,Hawaii,Idaho,Illinois,Indiana,Iowa,Kansas,Kentucky, Louisiana,Maine,Montana,Nebraska,Nevada,New Hampshire,New Jersey,New Mexico,New York, North Carolina,North Dakota,Ohio,Oklahoma,Oregon,Maryland,Massachusetts,Michigan, Minnesota,Mississippi,Missouri,Pennsylvania,Rhode Island,South Carolina,South Dakota, Tennessee,Texas,Utah,Vermont,Virginia,Washington,West Virginia,Wisconsin,Wyoming".split(',');
''Tested with: 1.91+, IE 7/8/9, Firefox 18''


$('#question'+q1ID+' input.text').autocomplete(states, {
This workaround uses the [http://jqueryui.com/demos/autocomplete/ Autocomplete widget] included with jQuery UI and the [https://code.google.com/p/jquery-csv/downloads/list jquery.csv.js] plugin to provide a respondent with a list of suggested answers as they type in a text input and then to automatically populate other fields depending on the selection.
matchContains: true,
minChars: 0
});
  });


</script></syntaxhighlight>
For example, if you have 3 questions - "Name", "ID" and "Email" - you can use this workaround to autocomplete the "Name" question and then the "ID" and "Email" questions can be automatically populated with values associated with the selected name.
*Download the [https://code.google.com/p/jquery-csv/downloads/list jquery.csv.js] plugin and place it in your template folder
*If using LimeSurvey 1.9x, add the following line to your startpage.pstpl BEFORE the tag for template.js, if using 2.x, add it AFTER the {TEMPLATEJS} placeholder<syntaxhighlight lang="php"><script type="text/javascript" src="{TEMPLATEURL}jquery.csv.js"></script></syntaxhighlight>
*Create your CSV file with 3 columns (name, ID, email) as in this example and place it in your template folder<syntaxhighlight lang="php">Bob,    Bob's ID,    Bob's email


'''Method 2 ("Countries" question in the demo):'''
Andy,    Andy's ID,    Andy's email
*Place the CSV file of suggestions in your template directory.
*Place a PHP file containing the code below in your template directory
*Replace "countries2.csv" with your CSV filename (you could make this more universal by passing the filename in the URL)
*This file will grab the contents of the CSV and encode them in JSON format that the JavaScript can use
*Place the JavaScript code below in ths source of the text question
*Replace "QQ" with the ID of the text input question
*Replace "templates/yourTemplate/countries.php" with the path to the PHP file (from LimeSurvey root)
*This script will grab the jsonized data from the PHP file and load it into an array that the plugin can use


PHP code:
Sam,    Sam's ID,    Sam's email


<syntaxhighlight lang="php" enclose="div"><?php
Tony,    Tony's ID,    Tony's email


$countriesArr = array();
Fred,    Fred's ID,    Fred's email</syntaxhighlight>
*Create your 3 questions - "Name", "ID" and "Email". They must be in the same group/page.
*Add the following styles to the end of your 'template.css' file<syntaxhighlight lang="php">/* Autocomplete styles */


$file_handle = fopen("countries2.csv", "r");
ul.ui-autocomplete {


while (!feof($file_handle) ) {
  width: 250px !important;
$line_of_text = fgetcsv($file_handle);
array_push($countriesArr, $line_of_text[0]);
}


fclose($file_handle);
  padding: 5px 10px;


echo json_encode($countriesArr);
  list-style: none;


?></syntaxhighlight>
}</syntaxhighlight>
*Add the following script to the source of one of the questions
*Replace:
**"NN" with the "Name" question ID
**"II" with the "ID" question ID
**"EE" with the "Email" question ID
*Modify the path to your CSV file<syntaxhighlight lang="php"><script type="text/javascript" charset="utf-8">


JavaScript code:
$(document).ready(function() {


<syntaxhighlight lang="javascript" enclose="div"><script type="text/javascript" charset="utf-8">
var qNameID = NN;


$(document).ready(function() {
var qIDID = II;


var qID = QQ;
var qEmailID = EE;
var url = "templates/yourTemplate/countries.php";
var countriesArr = new Array();


$.getJSON(url,function(data){
var url = "upload/templates/yourTemplate/yourCsvFile.csv";


$(data).each(function(i, item){
// Create an array to hold the names
countriesArr.push(item);
var namesArr = new Array();
});


$('#question'+qID+' input.text').autocomplete(countriesArr, {
// Grab the CSV contents
matchContains: true,
$.get(url,function(data){
minChars: 0
 
});
// Convert CSV contents to an array of arrays
fullArray = $.csv.toArrays(data);
 
// Load the names array
$(fullArray).each(function(i, item){
namesArr.push(item[0]);
});
 
// Initialise the autocomplete plugin
$('#question'+qNameID+' input.text').autocomplete({
source: namesArr,
// Event fired when a selection is made (ui.item.value refers to the selected item)
select: function(event, ui) {
// Find the "ID" and "Email" values associated with the selected name value and load those questions
$(fullArray).each(function(i, item){
if(item[0] == ui.item.value) {
// The value from column 2 of the CSV
$('#question'+qIDID+' input.text').val(item[1]);
// The value from column 3 of the CSV
$('#question'+qEmailID+' input.text').val(item[2]);
}
});
}
});
});
});
});
});
Line 2,949: Line 3,307:
</script></syntaxhighlight>
</script></syntaxhighlight>


== 1.91 and later versions==
=Create MaxDiff question type=
 
See the [[Workarounds: Question design, layout and templating|Question design, layout and templating]] section for a workaround that uses JavaScript to convert an '''''Array (flexible labels) by column''''' question into a [http://en.wikipedia.org/wiki/MaxDiff MaxDiff] question type.


''Tested with: 1.91+, IE 6/7/8, Firefox 3.6, Safari 3.2''
[[File:max_diff_596x253.gif]]


This workaround uses the [http://jqueryui.com/demos/autocomplete/ Autocomplete widget] included with jQuery UI to provide a respondent a list of suggested answers that the they can select from as they type in a text input.
=In a checkbox matrix, have the 'none' option in a row disable all other columns in that row=


Although the plugin allows for much more complex applications, the two that I use most frequently are outlined below. The first method uses a relatively small list of suggestions that is stored in an array locally. The second is a little more complex - it uses a remote CSV file of suggestions that is accessed through AJAX an PHP.
''Tested with: LimeSurvey 1.90+''


'''Both methods:'''
The questionId parameter is the base ID of the question matrix (multiflexible).
*Add the following styles to the end of your 'template.css' file


<syntaxhighlight lang="css" enclose="div">/* Autocomplete styles */
numColumns and numRows speak for themselves


ul.ui-autocomplete {
specialColumns is an array of the columns that have the special ability that when one of them
width: 250px !important;
 
padding: 5px 10px;
is checked, none of the others can be checked. E.g., a 'none of these' and/or a 'do now know' option.
list-style: none;
}</syntaxhighlight>


'''Method 1 (use local JavaScript array for data):'''
<syntaxhighlight lang="php">
*Create a text input question
*Add the following script to the source of the text question
*Replace "QQ" with the ID of the text input question
*Replace the states with your list of suggestions (comma separated)


<syntaxhighlight lang="javascript" enclose="div"><script type="text/javascript" charset="utf-8">
<script>


$(document).ready(function() {
numColumns=13;


var q1ID = 'QQ';
numRows=18;


var states = "Alabama,Alaska,Arizona,Arkansas,California,Colorado,Connecticut,Delaware, District of Columbia,Florida,Georgia,Hawaii,Idaho,Illinois,Indiana,Iowa,Kansas,Kentucky, Louisiana,Maine,Montana,Nebraska,Nevada,New Hampshire,New Jersey,New Mexico,New York, North Carolina,North Dakota,Ohio,Oklahoma,Oregon,Maryland,Massachusetts,Michigan, Minnesota,Mississippi,Missouri,Pennsylvania,Rhode Island,South Carolina,South Dakota, Tennessee,Texas,Utah,Vermont,Virginia,Washington,West Virginia,Wisconsin,Wyoming".split(',');
questionId='28417X2X24';


$('#question'+q1ID+' input.text').autocomplete({
specialColumns=[1,12,13];
source: states
});


});
function action(obj){


</script></syntaxhighlight>
  locator=obj.id.replace('cbox_'+questionId, '').split('_');


'''Method 2 (use remote CSV file for data):'''
  row=locator[0];
*Place the CSV file of suggestions in your template directory
*Place a PHP file containing the code below in your template directory
*Replace "countries2.csv" with your CSV filename (you could make this more universal by passing the filename in the URL)
*This file will grab the contents of the CSV and encode them in JSON format that the JavaScript can use
*Place the JavaScript code below in ths source of the text question
*Replace "QQ" with the ID of the text input question
*Replace "templates/yourTemplate/countries.php" with the path to the PHP file (from LimeSurvey root)
*This script will grab the jsonized data from the PHP file and load it into an array that the plugin can use


PHP code:
  column=locator[1];


<syntaxhighlight lang="php" enclose="div"><?php
  //alert('Action for row,column='+row+','+column);


$countriesArr = array();
  my_id='#cbox_'+questionId+row+'_'+column+''


$file_handle = fopen("countries2.csv", "r");
  if($(my_id).attr('checked') == false) {


while (!feof($file_handle) ) {
      for( var i=1; i<=numColumns; i++ ) {
$line_of_text = fgetcsv($file_handle);
array_push($countriesArr, $line_of_text[0]);
}


fclose($file_handle);
          if(i==column) continue;


echo json_encode($countriesArr);
          id='#cbox_'+questionId+row+'_'+i


?></syntaxhighlight>
          $(id).attr('disabled',false);


JavaScript code:
      }


<syntaxhighlight lang="javascript" enclose="div"><script type="text/javascript" charset="utf-8">
  } else {


$(document).ready(function() {
      for( var i=1; i<=numColumns; i++ ) {


var qID = QQ;
          if(i==column) continue;
var url = "templates/yourTemplate/countries.php";
var dataArr = [];


$.getJSON(url,function(data){
          id='#cbox_'+questionId+row+'_'+i


$.each(data,function(i, item){
          aid='#answer'+questionId+row+'_'+i
dataArr.push(item);
});


$('#question'+qID+' input.text').autocomplete({
          $(aid).val(0);
source: dataArr
});


});
          $(id).attr('checked',false);


});
          $(id).attr('disabled',true);


</script></syntaxhighlight>
      }


== Use autocomplete to populate several fields==
  }


''Tested with: 1.91+, IE 7/8/9, Firefox 18''
}


This workaround uses the [http://jqueryui.com/demos/autocomplete/ Autocomplete widget] included with jQuery UI and the [https://code.google.com/p/jquery-csv/downloads/list jquery.csv.js] plugin to provide a respondent with a list of suggested answers as they type in a text input and then to automatically populate other fields depending on the selection.
function customOnLoad(){


For example, if you have 3 questions - "Name", "ID" and "Email" - you can use this workaround to autocomplete the "Name" question and then the "ID" and "Email" questions can be automatically populated with values associated with the selected name.
  for( var row=1; row<=numRows; row++ ) {
*Download the [https://code.google.com/p/jquery-csv/downloads/list jquery.csv.js] plugin and place it in your template folder
*If using LimeSurvey 1.9x, add the following line to your startpage.pstpl BEFORE the tag for template.js, if using 2.x, add it AFTER the {TEMPLATEJS} placeholder<syntaxhighlight lang="php" enclose="div"><script type="text/javascript" src="{TEMPLATEURL}jquery.csv.js"></script></syntaxhighlight>
*Create your CSV file with 3 columns (name, ID, email) as in this example and place it in your template folder<syntaxhighlight lang="php" enclose="div">Bob,    Bob's ID,    Bob's email


Andy,    Andy's ID,    Andy's email
      for( var k=0; k<specialColumns.length; k++ )
 
      {


Sam,    Sam's ID,    Sam's email
          column=specialColumns[k];


Tony,    Tony's ID,    Tony's email
          $('#cbox_'+questionId+row+'_'+column).change(function() {


Fred,    Fred's ID,    Fred's email</syntaxhighlight>
              action(this);
*Create your 3 questions - "Name", "ID" and "Email". They must be in the same group/page.
*Add the following styles to the end of your 'template.css' file<syntaxhighlight lang="php" enclose="div">/* Autocomplete styles */


ul.ui-autocomplete {
          });


  width: 250px !important;
      }


   padding: 5px 10px;
   }


  list-style: none;
}


}</syntaxhighlight>
jQuery(document).ready(
*Add the following script to the source of one of the questions
*Replace:
**"NN" with the "Name" question ID
**"II" with the "ID" question ID
**"EE" with the "Email" question ID
*Modify the path to your CSV file<syntaxhighlight lang="php" enclose="div"><script type="text/javascript" charset="utf-8">


$(document).ready(function() {
  function(){


var qNameID = NN;
  customOnLoad();


var qIDID = II;
  }


var qEmailID = EE;
);


var url = "upload/templates/yourTemplate/yourCsvFile.csv";
</script>


// Create an array to hold the names
</syntaxhighlight>
var namesArr = new Array();


// Grab the CSV contents
=Last Option In Array (Numbers) (Checkboxes) Row Excludes All Others=
$.get(url,function(data){


// Convert CSV contents to an array of arrays
''Tested with LimeSurvey Versions 2.06 and 2.54''
fullArray = $.csv.toArrays(data);


// Load the names array
This workaround uses JavaScript/jQuery to make the last option of each Array (Numbers) (Checkboxes) row exclude all other options in the same row.
$(fullArray).each(function(i, item){
 
namesArr.push(item[0]);
The script will uncheck all options in a row if the last option is checked and uncheck the last option in a row if any other options are checked.
});


// Initialise the autocomplete plugin
==Implementation LS Version 2.06:==
$('#question'+qNameID+' input.text').autocomplete({
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
source: namesArr,
#Place the following script in the source of the array question.<syntaxhighlight lang="javascript">
// Event fired when a selection is made (ui.item.value refers to the selected item)
<script type="text/javascript" charset="utf-8">
select: function(event, ui) {
$(document).ready(function() {
// Find the "ID" and "Email" values associated with the selected name value and load those questions
// Call the exclude function using question ID
$(fullArray).each(function(i, item){
excludeOpt({QID});
if(item[0] == ui.item.value) {
// The value from column 2 of the CSV
$('#question'+qIDID+' input.text').val(item[1]);
// The value from column 3 of the CSV
$('#question'+qEmailID+' input.text').val(item[2]);
}
});
}
});
});
});
});
 
// A function to make the last option in each array row exclusive
function excludeOpt (qID) {
 
var thisQuestion = $('#question'+qID)


</script></syntaxhighlight>
// Add some classes to the checkbox cells
$('td.checkbox-item', thisQuestion).addClass('normalOpt');
$('tr.subquestions-list', thisQuestion).each(function(i) {
$('.normalOpt:last', this).removeClass('normalOpt').addClass('exlusiveOpt')
});


=Drag and Drop Rankings=
// A listener on the checkbox cells
$('td.checkbox-item', thisQuestion).click(function (event) {
handleExclusive($(this));
});


{{Deprecated|2.0}}
// A listener on the checkboxes
$('td.checkbox-item input[type="checkbox"]', thisQuestion).click(function (event) {
handleExclusive($(this).closest('td'));
});
function handleExclusive(thisCell) {
 
var thisRow = $(thisCell).closest('tr');


{{Hint|Text=In 2.0 and up, this system Ranking question use jqueryui-sortable. This workaround is deprecated. }}  
// Uncheck the appropriate boxes in a row
 
if ($(thisCell).hasClass('normalOpt')) {
==Drag and Drop Ranking - Version 1.90 to 1.92==
$('.exlusiveOpt input[type="checkbox"]', thisRow).attr('checked', false);
 
}
''Tested with: 1.90+ & 1.91RC4, IE 6/7/8, Firefox 3.6, Safari 3.2''
else {
$('.normalOpt input[type="checkbox"]', thisRow).attr('checked', false);
}
// Check conditions (relevance)
$('td.checkbox-item', thisRow).each(function(i) {
var thisValue = '';
if($('input[type="checkbox"]', this).is(':checked')) {
thisValue = 1;
}
var thisSGQA = $('input[type="checkbox"]', this).attr('id').replace(/cbox_/, '');
$('input[type="hidden"]', this).attr('value', thisValue);
fixnum_checkconditions(thisValue, thisSGQA, 'hidden');
});
}
}
</script></syntaxhighlight>
 
==Implementation LS Version 3.x:==
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
#Place the following script in the source of the array question.<syntaxhighlight lang="javascript">
<script type="text/javascript" charset="utf-8">
$(document).on('ready pjax:scriptcomplete',function(){
 
var thisQuestion = $('#question{QID}')


{{Hint|Text=In 2.0 and up, this system Ranking question use jqueryui-sortable. This workaround is deprecated. }}  
// Add some classes to the checkbox cells
$('td.checkbox-item:last-child', thisQuestion).addClass('exclusive-item');
// A function to un-check boxes
function resetCheckbox(thisItem) {
$(':hidden', thisItem).val('');
$(':checkbox', thisItem).prop('checked', false).trigger('change');
}
// A listener on the checkboxes
$(':checkbox', thisQuestion).on('change', function(e) {
if($(this).is(':checked')) {
var thisItem = $(this).closest('.answer-item');
var thisRow = $(this).closest('tr');
var items = $('td.answer-item.exclusive-item', thisRow);
if($(thisItem).hasClass('exclusive-item')) {
items = $('td.answer-item:not(.exclusive-item)', thisRow);
}
$.each(items, function(i, el) {
resetCheckbox(el);
});
}
});
});
</script></syntaxhighlight>


This workaround is a modification of the workaround below and also uses [http://jqueryui.com/demos/sortable/#connect-lists JQuery connected sortables]. Thanks to the author of that workaround.
=Add Navigation Buttons (Next and Previous) To Top Of Page=


[[File:Untitled-12.png]]
''Tested with: 1.90+, IE 7/8, Firefox 3.6, Safari 3.2''


Modifications include:
Add the following to the end of template.js. It will insert a new div after the progress bar and then insert clones of the nav buttons in the new div.
*Allow for multiple drag-n-drop ranking questions on a page
*Handle minimum and maximum answers settings
*Allow double clicking of items to add or remove them from the ranking list
*Work with the new shipped jQuery UI


There is a '''[http://www.partnersinc.biz/surveys//index.php?sid=36981&newtest=Y&lang=en demonstrated here]'''.
This example is for the default template in 1.90 or 1.91. The placement of the new div may need to be modified for other templates.


Implementation is as follows:
<syntaxhighlight lang="javascript">
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
  $(document).ready(function() {
#Place the large script below in your template.js file.
#Place the small script below in the source a ranking question, (this applies the "dragDropRank" function to the question). Replace "QQ" with the ranking [http://docs.limesurvey.org/SGQA+identifier&structure;=English+Instructions+for+LimeSurvey#General_Description question ID].
**Note: the LimeSurvey translations will be automatically used for the "Choices" and "Rankings" header labels but can be overridden with custom labels - see the example calls below.
#Insert the CSS below at the end of template.css (these styles are for the default template).


NOTE: I have not tested this workaround with conditions.
// Insert a new div after the progress bar
$('<div id="navigator2" />').insertAfter('#progress-wrapper');


'''Code for template.js:'''
// Style the new div
$('#navigator2').css({
'text-align':'center'
});


<syntaxhighlight lang="javascript" enclose="div">
// Insert clones of the nav buttons in the new div
function dragDropRank(qID, choiceText, rankText) {
$('input.submit:eq(0)').clone().appendTo('#navigator2');
$('input.submit:eq(2)').clone().appendTo('#navigator2');


if(!choiceText) {
// Give the new buttons some new IDs
choiceText = $('#question'+qID+' td.label label').text();
$('#navigator2 input.submit').each(function(i) {
}
var oldID = $(this).attr('id');
if(!rankText) {
var newID = oldID + '2';
rankText = $('#question'+qID+' td.output tr:first td:eq(1)').text();
$(this).attr('id', newID)
}
});
});</syntaxhighlight>
//Add a class to the question
 
$('#question'+qID+'').addClass('dragDropRanking');
[[File:extra_buttons.png]]
 
// Hide the original question in LimeSurvey (so that we can replace with drag and drop)
=How to ''add'' an opt out link to your survey=
$('#question'+qID+' .rank, #question'+qID+' .questionhelp').hide();
// Turn off display of question (to override error checking built into LimeSurvey ranking question)
//$('#display'+qID).val('off');
// Add connected sortables elements to the question
var htmlCode = '<table class="dragDropTable"> \
<tbody> \
<tr> \
<td> \
<span class="dragDropHeader choicesLabel">'+choiceText+'</span><br /> \
<div class="ui-state-highlight dragDropChoices"> \
<ul id="sortable1'+qID+'" class="connectedSortable'+qID+' dragDropChoiceList"> \
<li>Choices</li> \
</ul> \
</div> \
</td> \
<td> \
<span class="dragDropHeader rankingLabel">'+rankText+'</span><br /> \
<div class="ui-state-highlight dragDropRanks"> \
<ol id="sortable2'+qID+'" class="connectedSortable'+qID+' dragDropRankList"> \
<li>Ranks</li> \
</ol> \
</div> \
</td> \
</tr> \
</tbody> \
</table>';
$(htmlCode).insertAfter('#question'+qID+' table.rank');
// Remove placeholder list items (have to have one item so that LimeSurvey doesn"t remove the <ul>)
$('#sortable1'+qID+' li, #sortable2'+qID+' li').remove();
// Load any previously-set values
loadDragDropRank(qID);
// Find the number of LimeSurvey ranking inputs ("Maximum answers")
var maxAnswer = $('#question'+qID+' td.output input.text').length;
// Set up the connected sortable
$('#sortable1'+qID+', #sortable2'+qID+'').sortable({
connectWith: '.connectedSortable'+qID+'',
placeholder: 'ui-sortable-placeholder',
helper: 'clone',
revert: 50,
receive: function(event, ui) {
if($('#sortable2'+qID+' li').length > maxAnswer) {
alert ('You can only rank a maximum of '+maxAnswer+' choices.');
$(ui.sender).sortable('cancel');
}
},
stop: function(event, ui) {
$('#sortable1'+qID+'').sortable('refresh');
$('#sortable2'+qID+'').sortable('refresh');
updateDragDropRank(qID);
}
}).disableSelection();
// Get the list of choices from the LimeSurvey question and copy them as items into the sortable choices list
$('#CHOICES_' + qID).children().each(function(index, Element) {
var liCode = '<li class="ui-state-default" id="choice_' + $(this).attr("value") + '">' + this.text + '</li>'
$(liCode).appendTo('#sortable1'+qID+'');
});
// Allow users to double click to move to selections from list to list
$('#sortable1'+qID+' li').live('dblclick', function() {
if($('#sortable2'+qID+' li').length == maxAnswer) {
alert ('You can only rank a maximum of '+maxAnswer+' choices.');
return false;
}
else {
$(this).appendTo('#sortable2'+qID+'');
$('#sortable1'+qID+'').sortable('refresh');
$('#sortable2'+qID+'').sortable('refresh');
updateDragDropRank(qID);
}
});
$('#sortable2'+qID+' li').live('dblclick', function() {
$(this).appendTo('#sortable1'+qID+'');
$('#sortable2'+qID+'').sortable('refresh');
$('#sortable1'+qID+'').sortable('refresh');
updateDragDropRank(qID);
});
}


// This function copies the drag and drop sortable data into the LimeSurvey ranking list.
Tested with: 1.91+, Firefox 5.0
function updateDragDropRank(qID) {


// Reload the LimeSurvey choices select element
This javascript inserts a link to opt out of the survey above the progress bar.
var rankees = [];
$('#sortable2'+qID+' li').each(function(index) {
$(this).attr('value', index+1);
// Get value of ranked item
var liID = $(this).attr("id");
liIDArray = liID.split('_');
// Save to an array
rankees[rankees.length] = { "r_name":$(this).text(),"r_value":liIDArray[1] };
});
$('#question'+qID+' input[name<div class="simplebox">="RANK_"]').each(function(index) {
if (rankees.length > index) {
$(this).val(rankees[index].r_name);
$(this).next('input').attr('value', rankees[index].r_value);
} else {
$(this).val('');
$(this).next().val('');
}
});
// Reload the LimeSurvey choices select element (we need this for "Minimum answers" code)
$('#question'+qID+' td.label select option').remove();
$('#sortable1'+qID+' li').each(function(index) {
var liText = $(this).text();
var liID = $(this).attr('id');
liIDArray = liID.split('_');
var optionCode = '<option value="'+liIDArray[1]+'">'+liText+'</option>';
$(optionCode).appendTo('#question'+qID+' td.label select');
});
// Hack for IE6
if ($.browser.msie && $.browser.version.substr(0,1)<7) {
$('#question'+qID+' select').show();
$('#question'+qID+' select').hide();
}
}


// This function is called on page load to see if there are any items already ranked (from a previous visit
[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|How to use script here]]
// or cached due to a page change. If so, the already-ranked items are loaded into the sortable list
function loadDragDropRank(qID) {


var rankees = [];
Then insert the following code into to the source of a group description or a question. I used the group description.
 
// Loop through each item in the built-in LimeSurvey ranking list looking for non-empty values
<syntaxhighlight lang="php"> <script type="text/javascript" charset="utf-8">
$('#question'+qID+' input[name</div>="RANK_"]').each(function(index) {
 
  $(document).ready(function() {
// Check to see if the current item has a value
 
if ($(this).val()) {
// Find the survey ID
if($('input#fieldnames').length != 0) {
// Item has a value - save to the array
var fieldNames = $('input#fieldnames').attr('value');
// Use this element to contain the name and the next for the value (numeric)
var tmp = fieldNames.split('X');
rankees[rankees.length] = { "r_name":$(this).val(),"r_value":$(this).next().val() };
var sID = tmp[0];
}
}
});
// Now that we have a list of all the pre-ranked items, populate the sortable list
// Note that the items *won"t* appear in the main list because LimeSurvey has removed them.
$.each(rankees, function(index, value) {
// Create the items in the sortable
var liCode = '<li class="ui-state-default" id="choice_' + value.r_value + '">' + value.r_name + '</li>';
$(liCode).appendTo('#sortable2'+qID+'');
});
}</syntaxhighlight>


'''Code in the source of the question(s) using the LimeSurvey translations for the "Choices" and "Rankings" element header labels''' (replace "QQ" with the ranking [http://docs.limesurvey.org/SGQA+identifier&structure;=English+Instructions+for+LimeSurvey#General_Description question ID]):
// Insert a new div after the progress bar
$('<div id="optOutLink" />').insertBefore('#progress-wrapper');
 
// Style the new div
$('#optOutLink').css({
'text-align':'center'
});


<syntaxhighlight lang="javascript" enclose="div">
// Insert opt out url in the new div
<script type="text/javascript" charset="utf-8">
$('#optOutLink').html('<p><a href="optout.php?lang=en&sid;='+sID+'&token;={TOKEN}">Click here to opt out of this survey.</a></p><br>');


$(document).ready(function() {
dragDropRank(QQ);
});
});


</script></syntaxhighlight>
</script></syntaxhighlight>


'''Fix for later versions of 1.91+'''
=Anonymously track respondents answers across multiple surveys=


In my version of LimeSurvey 1.91+, build 11026, this drag-n-drop workaround was not saving the selections properly. I'm not sure if that's because of my template, or other jquery uses, but I fixed it by making the following modifications to the above code placed into template.js.
''Tested with: 1.91+ and 1.92+''


If you're having problems (and it's definitely worth testing that the rankings are actually saving into your response table before proceeding with the survey) give this a try.
This workaround uses JavaScript/jQuery to enable tracking of respondents across multiple surveys ensuring anonymity (it does not use tokens).


In the 'updateDragDropRank(qID)' function, replace the line:
To do this you have to use this question in all the surveys you want to link respondents answers.
<syntaxhighlight  lang="javascript" enclose="div">
$('#question'+qID+' input[name<div class="simplebox">="RANK_"]').each(function(index) {
</syntaxhighlight>
with:
<syntaxhighlight lang="javascript" enclose="div">
$('#question'+qID+' input[name^="RANK_"]').each(function(index) {
</syntaxhighlight>


In the 'loadDragDropRank(qID) function, replace the line:
This workaround create an ID by asking some partial personal information (like initials, birth month ...). The ID will be encrypted using a one-way function (in this case SHA-1) and stored in a short text answer.
<syntaxhighlight lang="javascript" enclose="div">
$('#question'+qID+' input[name</div>="RANK_"]').each(function(index) {
</syntaxhighlight>
with:
<syntaxhighlight lang="javascript" enclose="div">
$('#question'+qID+' input[name^="RANK_"]').each(function(index) {
</syntaxhighlight>


Implementation is as follows:
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
#Place the following code in the source of a short text question.
#Optional: you can also add other select field to avoid collisions (e.g. for large population sample)


'''Code in the source of the question(s) using custom text for the "Choices" and "Rankings" element header labels''' (replace "QQ" with the ranking [http://docs.limesurvey.org/SGQA+identifier&structure;=English+Instructions+for+LimeSurvey#General_Description question ID]):
<syntaxhighlight lang="php">


<syntaxhighlight lang="javascript" enclose="div">
<p>This answers are used to generate your ID. These information will be encrypted, it will not be possible in any case to identify you neither from your answers nor from your ID.</p>


<script type="text/javascript" charset="utf-8">
<table>


$(document).ready(function() {
<tr>
dragDropRank(QQ, 'Custom Choices Label', 'Custom Rankings Label');
});


</script></syntaxhighlight>
<td>Initial of your name</td>


'''Styles for template.css:'''
<td>


<syntaxhighlight lang="css" enclose="div">
<select id="field1">
/* Drag-n-drop ranking styles */
.dragDropTable {
border: 0 none;
border-collapse: collapse;
width: 100%;
}


.dragDropTable td {
  <option value=""></option>
vertical-align: top;
width: 50%;
padding-right: 20px;
}


.dragDropTable .dragDropHeader {
  <option value="A">A</option>
font-weight: bold;
}


.dragDropTable .dragDropChoices,
  <option value="B">B</option>
.dragDropTable .dragDropRanks {
margin: 5px 0 0 0;
background: transparent none;
border: 0 none;
}


.dragDropTable .dragDropChoiceList,
  <option value="C">C</option>
.dragDropTable .dragDropRankList {
float: left;
width: 100%;
min-height: 2.4em;
_height: 2.4em; /* IE6 and below hack */
margin: 0;
padding: 0;
list-style-type: none;
background: #FBFBFB none;
border: 1px solid #CCCCCC;
}


.dragDropTable .dragDropChoiceList li,
  <option value="D">D</option>
.dragDropTable .dragDropRankList li {
margin: 3px;
*margin-left: -13px; /* IE7 and below */
padding: 3px;
min-height: 1.3em;
_height: 1.3em; /* IE6 and below hack */
font-weight: normal;
cursor: move;
display: block; /* Force the li to full width */
}</syntaxhighlight>


==Drag and Drop Ranking using Images - Version 1.90 to 1.92==
  <option value="E">E</option>


''Tested with: 1.90+ & 1.91RC4, IE 6/7/8, Firefox 3.6, Safari 3.2''
  <option value="F">F</option>


{{Hint|Text=In 2.0 and up, this system Ranking question use jqueryui-sortable. This workaround is deprecated. }}
  <option value="G">G</option>


This workaround works in conjunction with the above workaround to allow ranking of images.
  <option value="H">H</option>


[[File:Untitled-11.png]]
  <option value="I">I</option>


There is a '''[http://www.partnersinc.biz/surveys//index.php?sid=36981&newtest=Y&lang=en demonstrated here]'''.
  <option value="J">J</option>


Implementation is as follows:
  <option value="K">K</option>


1) Follow the above workaround to get basic drag-n-drop functionality in your ranking question.
  <option value="L">L</option>


2) Insert your images in the ranking question text or help section after any text that you wish to have there (we'll move them later with JavaScript).
  <option value="M">M</option>


3) Give the images '''EXACTLY''' the same IDs as the answer code you would like them to be associated with:[[File:Untitled-8.png]]
  <option value="N">N</option>


4) Place the first script below in your template.js file.
  <option value="O">O</option>


5) Place the second script below in the source the ranking question '''AFTER''' the initial call for the dragDropRank function from the workaround above. Replace "QQ" with the ID of the ranking question (this applies the "dragDropRankImages" function to the question).
  <option value="P">P</option>


6) Insert the CSS below at the end of template.css. Replace "11" with your question ID. These styles are for the default template using 50x50px images (as in the demo) and may need to be modified for other templates or image sizes.
  <option value="Q">Q</option>


'''Code for template.js:'''
  <option value="R">R</option>


<syntaxhighlight lang="javascript" enclose="div">
  <option value="S">S</option>


function dragDropRankImages(qID) {
  <option value="T">T</option>


$('.connectedSortable'+qID+' li').each(function(i) {
  <option value="U">U</option>


// Remove any text in the sortable choice or rank items
  <option value="V">V</option>
$(this).text('');


// Move the images into the appropriate sortable list item
  <option value="W">W</option>
var liID = $(this).attr('id');
 
liIDArray = liID.split('_');
  <option value="X">X</option>
$('#question'+qID+' img#'+liIDArray[1]+'').appendTo(this);
});
}</syntaxhighlight>


'''Code in the source of the question(s):'''
  <option value="Y">Y</option>


<syntaxhighlight lang="javascript" enclose="div">
  <option value="Z">Z</option>


<script type="text/javascript" charset="utf-8">
</select>
 
</td>


$(document).ready(function() {
</tr>
dragDropRankImages(QQ);
});


</script></syntaxhighlight>
<tr>


'''Styles for template.css:'''
<td>Your birth month</td>


<syntaxhighlight lang="css" enclose="div">
<td>


/* Drag-n-drop for ranking images */
<select id="field2">
#question11 .dragDropTable {
width: auto;
}


#question11 .dragDropTable .dragDropChoiceList,
  <option value=""></option>
#question11 .dragDropTable .dragDropRankList {
min-height: 60px;
_height: 60px; /* IE6 and below hack */
width: auto;
min-width: 60px;
_width: 60px; /* IE6 and below hack */
}


#question11 .dragDropTable li {
  <option value="01">January</option>
background: 0 none;
border: 0 none;
height: 50px;
width: 50px;
}


#question11 .dragDropTable li img {
  <option value="02">February</option>
margin: 0;
padding: 0;
border: 0 none;
}</syntaxhighlight>


==Drag and Drop Ranking for iPad - Version 1.91 and newer==
  <option value="03">March</option>


''Tested with: LimeSurvey 1.91, iPad 2''
  <option value="04">April</option>


This workaround extends the above workarounds to work with the touch interface on iPads.
  <option value="05">May</option>


1) Follow the [[Workarounds: Manipulating a survey at runtime using Javascript#Drag and Drop Ranking - Version 1.90 and newer|Drag and Drop Ranking - Version 1.90 and newer]] workaround above to get basic drag-n-drop functionality in your ranking question.
  <option value="06">June</option>


2) Download the [https://github.com/furf/jquery-ui-touch-punch jQuery.ui.touch-punch.js plugin] and save it in your template directory.
  <option value="07">July</option>


3) Replace this line in your startpage.pstpl:<syntaxhighlight lang="php" enclose="div"><script type="text/javascript" src="{TEMPLATEURL}template.js"></script></syntaxhighlight>With this:<syntaxhighlight lang="php" enclose="div"><script type="text/javascript" src="{TEMPLATEURL}jquery.ui.touch-punch.js"></script>
  <option value="08">August</option>


<script type="text/javascript" src="{TEMPLATEURL}template.js"></script></syntaxhighlight>
  <option value="09">September</option>


==Drag and Drop Ranking - Version 1.86==
  <option value="10">October</option>


Tested with 1.86 on IE 7,8, FireFox 3, Chrome, and Safari 4
  <option value="11">November</option>


{{Hint|Text=In 2.0 and up, this system Ranking question use jqueryui-sortable. This workaround is deprecated. }}
  <option value="12">December</option>


This workaround uses [http://jqueryui.com/demos/sortable/#connect-lists JQuery connected sortables] to modify a ranking question and allow the survey taker to drag and drop choices instead of clicking.  The nice thing is that the choices can be easily re-arranged or removed from the ranking, all by drag and drop.
</select>


I used it for a voting application and some of the element names reflect that use.
<td>


It looks best when some CSS is added to control the appearance of the boxers around each choice.
</tr>


[[File:drag-n-drop.png]]
</table>


<syntaxhighlight lang="javascript" enclose="div">
<script type="text/javascript" src="http://crypto-js.googlecode.com/svn/tags/3.0.2/build/rollups/sha1.js"></script>
// Question ID global variable
var questionID;
$(document).ready(function() {
// Grab the question ID[[File:drag-n-drop.png]]
// This is something with a class of "ranking" and an ID that starts with "questionXXXXX".  The X's represent the question ID
var entire_questionID = $(".ranking[id^='question']").attr('id');
// Get just the question number (ID)
var patt=/\d+$/;
questionID=patt.exec(entire_questionID);
// Hide the original quetion in LimeSurvey (so that we can replace with drag and drop)
$(".rank, .questionhelp").hide();
// Turn off display of question (to override error checking built into LimeSurvey ranking question)
$("#display"+questionID).val("off");
// Add connected sortables to the question
var html_code = "<table style='border: medium none ; width: 770px; margin-left: auto; \
margin-right: auto;'><tbody><tr><td style='width: 590px; vertical-align: top;'><h3>List of Candidates:</h3> \
<div class='ui-state-highlight' style='margin: 5px; float: left;' id='ballot_list'> \
<ul id='sortable1' class='connectedSortable'><li>Candidates</li></ul></div></td> \
<td style='vertical-align: top;'><h3>Your selections:</h3><div class='ui-state-highlight' \
style='margin: 5px; float: left;' id='voting_selections'><ol id='sortable2' class='connectedSortable'> \
<li>Voting Area</li></ol></div></td></tr></tbody></table>";
$(html_code).appendTo("#question" + questionID);
// Remove list items (have to have one item so that LimeSurvey doesn't remove the <ul>)
$("#sortable1 li, #sortable2 li").remove();
// Load any previously-set values
loadRanking();
// Load jQuery UI
$.getScript("/scripts/jquery/jquery-ui.js", function() {
// After JQuery UI finishes loading, set up the connected sortable.
$("#sortable1, #sortable2").sortable({
connectWith: '.connectedSortable',
placeholder: 'ui-sortable-placeholder',
stop: function(event, ui) {
$("#sortable1").sortable("refresh");
$("#sortable2").sortable("refresh");
updateRanking();
}
}).disableSelection();
// Get the list of candidates from the built-in LimeSurvey question and copy them as items in sortable1
$("#CHOICES_" + questionID).children().each(function(index, Element) {
li_tag = "<li class='ui-state-default' id='choice_" + $(this).attr('value') + "'>" + this.text + "</li>"
$(li_tag).appendTo("#sortable1"); } );
// Make sure the submit button is showing
$(":submit[value|=' Submit']").show();
// Show a confirmation alert upon submit
$("form").submit(function() {
// Override the built-in "disable navigation buttons" feature
$('#moveprevbtn, #movenextbtn, #movesubmitbtn').attr('disabled', '');
var validated = confirm("Cast your ballot now?");
if (validated) { return true; } else { return false; }
});
// Allow users to double click to move to selection list
$("#sortable1 li").assignDblClick();
});  // End of JQuery UI loading
}); // End of JQuery ready function
// Define the actions when an item is double clicked
$.fn.assignDblClick = function() {
$(this).dblclick(function(){
$(this).appendTo('#sortable2');
$("#sortable1").sortable("refresh");
$("#sortable2").sortable("refresh");
updateRanking();
});
}


// This function copies the drag and drop sortable data into the standard LimeSurvey ranking structure.
<script type="text/javascript">
function updateRanking() {
var rankees = [];
$('#sortable2 li').each(function(index) {
$(this).attr("value", index+1);
// Get value of person
li_tag = $(this).attr('id');
li_tag_array = li_tag.split("_");
// Save to an array
rankees[rankees.length] = { 'r_name':$(this).text(),'r_value':li_tag_array[1] };
});
$("input[name</div>='RANK_']").each(function(index) {
if (rankees.length > index) {
$(this).val(rankees[index].r_name);
$(this).next().val(rankees[index].r_value);
} else {
$(this).val("");
$(this).next().val("");
}
});
// Re-assign double click action to first list items and remove
// double click action from section list items
$('#sortable1 li').unbind('dblclick').assignDblClick();
$('#sortable2 li').unbind('dblclick');
}


// This function is called on page load to see if there are any items already ranked (from a previous visit
$(document).ready(function() {
// or cached due to a page change.  If so, the already-ranked items are loaded into the sortable list
function loadRanking() {
var rankees = [];
// Loop through each item in the built-in LimeSurvey ranking list looking for non-empty values
$("input[name<div class="simplebox">='RANK_']").each(function(index) {
// Check to see if the current item has a value
if ($(this).val()) {
// Item has a value - save to the array
// Use this element to contain the name and the next for the value (numeric)
rankees[rankees.length] = { 'r_name':$(this).val(),'r_value':$(this).next().val() };
}
});
// Now that we have a list of all the pre-ranked items, populate the sortable list
// Note that the items *won't* appear in the main list because LimeSurvey has removed them.
$.each(rankees, function(index, value) {
// Create the items in the sortable
li_tag = "<li class='ui-state-default' id='choice_" + value.r_value + "'>" + value.r_name + "</li>";
$(li_tag).appendTo("#sortable2");
});
}</syntaxhighlight>


=Create MaxDiff question type=
  $("#answer{SGQ}").attr('readonly', 'readonly');


See the [[Workarounds: Question design, layout and templating|Question design, layout and templating]] section for a workaround that uses JavaScript to convert an '''''Array (flexible labels) by column''''' question into a [http://en.wikipedia.org/wiki/MaxDiff MaxDiff] question type.
  //$("#answer{SGQ}").css('display', 'none'); //uncomment this line for not displaying the code


[[File:max_diff_596x253.gif]]
});


=In a checkbox matrix, have the 'none' option in a row disable all other columns in that row=
$('#field1, #field2').bind('change keyup', function() {


''Tested with: LimeSurvey 1.90+''
  if ($("#field1").val()=='' || $("#field2").val()=='')


The questionId parameter is the base ID of the question matrix (multiflexible).
      $("#answer{SGQ}").val('');


numColumns and numRows speak for themselves
  else {


specialColumns is an array of the columns that have the special ability that when one of them
      var sha1 = CryptoJS.algo.SHA1.create();


is checked, none of the others can be checked. E.g., a 'none of these' and/or a 'do now know' option.
      sha1.update($("#field1").val());


<syntaxhighlight lang="php" enclose="div">
      sha1.update($("#field2").val());


<script>
      // sha1.update($("#otherfield").val());


numColumns=13;
      var hash = sha1.finalize();


numRows=18;
      $("#answer{SGQ}").val(hash);


questionId='28417X2X24';
  }


specialColumns=[1,12,13];
});


function action(obj){
</script>


  locator=obj.id.replace('cbox_'+questionId, '').split('_');
</syntaxhighlight>


  row=locator[0];
=Check validity of international phone number in subquestion=


  column=locator[1];
''Tested with: 1.91+, Firefox 8, Chrome 15''


  //alert('Action for row,column='+row+','+column);
As of version 1.92, you can use the core Validation querstion option to validate multiple short text and array text questions. Any question that fails that validation check will have its background color changed to red.


  my_id='#cbox_'+questionId+row+'_'+column+''
Note, the below workaround will fail in version 1.92 because it contains a regular expression with curly braces.  However, as described above, support for this feature is now built-in to version 1.92


  if($(my_id).attr('checked') == false) {
This workaround uses javascript to check the validity of an international phone number in a subquestion of multiple short text.


      for( var i=1; i<=numColumns; i++ ) {
Add the following code to the source of your question.


          if(i==column) continue;
Replace "QQ" with the question ID and "NN" with the row number that you want to validate.


          id='#cbox_'+questionId+row+'_'+i
<syntaxhighlight lang="php">


          $(id).attr('disabled',false);
<script type="text/javascript" charset="utf-8">


      }
  $(document).ready(function() {


  } else {
      // Call the phone function with the question ID and the row number


       for( var i=1; i<=numColumns; i++ ) {
       phoneTest(QQ, NN);
 
      function phoneTest(qID, inputNum) {


           if(i==column) continue;
           // Interrupt next/submit function


           id='#cbox_'+questionId+row+'_'+i
           $('#movenextbtn, #movesubmitbtn').click(function(){


          aid='#answer'+questionId+row+'_'+i
              // Some vars - modify as required


          $(aid).val(0);
              var phoneMatch = /^[0-9,+,(), ,]{1,}(,[0-9]+){0,}$/;


          $(id).attr('checked',false);
              var msg1 = 'Please enter a valid phone number.';


          $(id).attr('disabled',true);
              // Test the input


      }
              var phoneInput = $('#question'+qID+' li:eq('+(inputNum-1)+') input.text').val();


  }
              if(phoneInput != '' && !phoneMatch.test(phoneInput)) {


}
                  alert(msg1);


function customOnLoad(){
                  $('#question'+qID+' li:eq('+(inputNum-1)+') input.text').css({ 'background':'pink' });


  for( var row=1; row<=numRows; row++ ) {
                  return false;


      for( var k=0; k<specialColumns.length; k++ )
              }


      {
              else {


          column=specialColumns[k];
                  $('#question'+qID+' li:eq('+(inputNum-1)+') input.text').css({ 'background':'' });


          $('#cbox_'+questionId+row+'_'+column).change(function() {
                  return true;


               action(this);
               }


           });
           });
Line 3,777: Line 3,820:
       }
       }


   }
   });


}
</script>


jQuery(document).ready(
</syntaxhighlight>


  function(){
The regex is from: [http://www.regexlib.com/REDetails.aspx?regexp_id=1136 regexlib.com blank]


  customOnLoad();
=Check validity of email address in subquestion=


  }
'Tested with: 1.91+, Firefox 8, Chrome 15''


);
<div class="simplebox">As of version 1.92, you can use the core Validation querstion option to validate multiple short text and array text questions. Any question that fails that validation check will have its background color changed to red.<br />Note, the below workaround will fail in version 1.92 because it contains a regular expression with curly braces.  However, as described above, support for this feature is now built-in to version 1.92</div>


</script>
This workaround uses javascript to check the validity of an email address in a subquestion of multiple short text.


</syntaxhighlight>
Add the following code to the source of your question.


=Last Option In Array (Numbers) (Checkboxes) Row Excludes All Others=
Replace "QQ" with the question ID and "NN" with the row number that you want to validate.


''Tested with: 2.05, IE 7-10, Firefox''
<syntaxhighlight lang="php">


This workaround uses JavaScript/jQuery to make the last option of each Array (Numbers) (Checkboxes) row exclude all other options in the same row.
<script type="text/javascript" charset="utf-8">


The script will uncheck all options in a row if the last option is checked and uncheck the last option in a row if any other options are checked.
  $(document).ready(function() {


Implementation is as follows:
      // Call the email function with the question ID and the row number
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
#Place the following script in the source of the array question.


NOTE: Care should be taken when using this script and conditional questions on the same page.
      emailTest(QQ, NN);


<syntaxhighlight lang="javascript" enclose="div">
      function emailTest(qID, inputNum) {
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
// Call the exclude function using question ID
excludeOpt ({QID});
});
// A function to make the last option in each array row exclusive
function excludeOpt (qID) {
var thisQuestion = $('#question'+qID)


// Add some classes to the checkbox cells
          // Interrupt next/submit function
$('table.subquestions-list tbody td', thisQuestion).addClass('normalOpt');
$('table.subquestions-list tbody tr', thisQuestion).each(function(i) {
$('.normalOpt:last', this).removeClass('normalOpt').addClass('exlusiveOpt')
});


// A listener on the checkbox cells
          $('#movenextbtn, #movesubmitbtn').click(function(){
$('table.subquestions-list tbody td', thisQuestion).click(function (event) {
// Set some vars
var thisRow = $(this).closest('tr');


// Uncheck the appropriate boxes in a row
              // Some vars - modify as required
if ($(this).hasClass('normalOpt')) {
$('.exlusiveOpt input[type=checkbox]', thisRow).attr('checked', false);
}
else {
$('.normalOpt input[type=checkbox]', thisRow).attr('checked', false);
}
});


// A listener on the checkboxes
              var emailMatch = /^[a-zA-Z0-9\_\-]+[a-zA-Z0-9\.\_\-]*@([a-zA-Z0-9\_\-]+\.)+([a-zA-Z]{2,4}|travel|museum)$/;
$('table.subquestions tbody td input[type=checkbox]', thisQuestion).click(function (event) {
// Set some vars
var thisRow = $(this).closest('tr');
var thisCell = $(this).closest('td');


// Uncheck the appropriate boxes in a row
              var msg1 = 'Please enter a valid email address.';
if ($(thisCell).hasClass('normalOpt')) {
$('.exlusiveOpt input[type=checkbox]', thisRow).attr('checked', false);
}
else {
$('.normalOpt input[type=checkbox]', thisRow).attr('checked', false);
}
});
}
</script></syntaxhighlight>


=Add Navigation Buttons (Next and Previous) To Top Of Page=
              // Test the input


''Tested with: 1.90+, IE 7/8, Firefox 3.6, Safari 3.2''
              var emailInput = $('#question'+qID+' li:eq('+(inputNum-1)+') input.text').val();


Add the following to the end of template.js. It will insert a new div after the progress bar and then insert clones of the nav buttons in the new div.
              if(emailInput != '' && !emailMatch.test(emailInput)) {


This example is for the default template in 1.90 or 1.91. The placement of the new div may need to be modified for other templates.
                  alert(msg1);


<syntaxhighlight lang="javascript" enclose="div">
                  $('#question'+qID+' li:eq('+(inputNum-1)+') input.text').css({ 'background':'pink' });
  $(document).ready(function() {


// Insert a new div after the progress bar
                  return false;
$('<div id="navigator2" />').insertAfter('#progress-wrapper');


// Style the new div
              }
$('#navigator2').css({
'text-align':'center'
});


// Insert clones of the nav buttons in the new div
              else {
$('input.submit:eq(0)').clone().appendTo('#navigator2');
$('input.submit:eq(2)').clone().appendTo('#navigator2');


// Give the new buttons some new IDs
                  $('#question'+qID+' li:eq('+(inputNum-1)+') input.text').css({ 'background':'' });
$('#navigator2 input.submit').each(function(i) {
var oldID = $(this).attr('id');
var newID = oldID + '2';
$(this).attr('id', newID)
});
});</syntaxhighlight>


[[File:extra_buttons.png]]
                  return true;


=How to ''add'' an opt out link to your survey=
              }


Tested with: 1.91+, Firefox 5.0
          });
 
      }


This javascript inserts a link to opt out of the survey above the progress bar.
  });


[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|How to use script here]]
</script>


Then insert the following code into to the source of a group description or a question. I used the group description.
</syntaxhighlight>


<syntaxhighlight lang="php" enclose="div"> <script type="text/javascript" charset="utf-8">
The regex is from: [http://www.regexlib.com/REDetails.aspx?regexp_id=1653 regexlib.com blank]


  $(document).ready(function() {
=Hiding the help-text when it's empty=


// Find the survey ID
Tested with: 1.91+
if($('input#fieldnames').length != 0) {
var fieldNames = $('input#fieldnames').attr('value');
var tmp = fieldNames.split('X');
var sID = tmp[0];
}


// Insert a new div after the progress bar
I had made a style in my template putting a border around my help-text. To avoid empty help-text to show up (and have lines instead of just 'nothing' when there was no help text, I used this piece of code in question.pstpl:
$('<div id="optOutLink" />').insertBefore('#progress-wrapper');


// Style the new div
<syntaxhighlight lang="php">
$('#optOutLink').css({
'text-align':'center'
});


// Insert opt out url in the new div
<script type="text/javascript">
$('#optOutLink').html('<p><a href="optout.php?lang=en&sid;='+sID+'&token;={TOKEN}">Click here to opt out of this survey.</a></p><br>');


});
                  var questionhelp="{QUESTIONHELP}";


</script></syntaxhighlight>
                  if (questionhelp=="") {


=Anonymously track respondents answers across multiple surveys=
                          document.write("");}


''Tested with: 1.91+ and 1.92+''
                  else {


This workaround uses JavaScript/jQuery to enable tracking of respondents across multiple surveys ensuring anonymity (it does not use tokens).
                  document.write('<tr><td class="survey-question-help">'+ questionhelp +'</td></tr>');}


To do this you have to use this question in all the surveys you want to link respondents answers.
              </script>


This workaround create an ID by asking some partial personal information (like initials, birth month ...). The ID will be encrypted using a one-way function (in this case SHA-1) and stored in a short text answer.
</syntaxhighlight>


Implementation is as follows:
it can probably be optimized but it worked for me
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
#Place the following code in the source of a short text question.
#Optional: you can also add other select field to avoid collisions (e.g. for large population sample)


<syntaxhighlight lang="php" enclose="div">
=Disable or hide selected fields in a matrix question=


<p>This answers are used to generate your ID. These information will be encrypted, it will not be possible in any case to identify you neither from your answers nor from your ID.</p>
''Tested with: 1.92+, IE 7/8/9, FireFox 12/13''


<table>
TASK


<tr>
Hide or disable selected fields in a Matrix, so that users cannot make any input.


<td>Initial of your name</td>
(See example, where users should not make input into red framed fields)


<td>
[[File:snap035.gif]]


<select id="field1">
Fig. 1: Prevent users from input into first two fields (as an example)


  <option value=""></option>
SOLUTION


  <option value="A">A</option>
First find the name of the fields you want to hide / disable. In my case I used Firebug with firefox to figure that out.


  <option value="B">B</option>
[[File:snap037.gif]]


  <option value="C">C</option>
Fig. 2: Figure out the name(s) of the field(s)


  <option value="D">D</option>
Then put the following script-code into the HTML-source code as described above:


  <option value="E">E</option>
Note: you have to change the code to match the names of your fields!


  <option value="F">F</option>
<syntaxhighlight lang="javascript">


  <option value="G">G</option>
<script type="text/javascript" charset="utf-8">


   <option value="H">H</option>
   $(document).ready(function() {


  <option value="I">I</option>
      $('input[name="69965X2X10SQ001_SQ001"]').attr('disabled', 'disabled');


  <option value="J">J</option>
      $('input[name="69965X2X10SQ001_SQ002"]').attr('hidden', 'hidden');


   <option value="K">K</option>
   });


  <option value="L">L</option>
</script>


  <option value="M">M</option>
</syntaxhighlight>


  <option value="N">N</option>
[[File:snap039.gif]]


  <option value="O">O</option>
Fig. 3: Result: first field is diabled ("greyed out"), second one is completely hidden


  <option value="P">P</option>
I think this way one can apply many other attributes to a field like color etc. But did not try that myself.


  <option value="Q">Q</option>
Thanks to roB2009 for the input in the forum!


  <option value="R">R</option>
=Add prefix or suffix to question type "Array (Texts)"=


  <option value="S">S</option>
TASK


  <option value="T">T</option>
Add a prefix or suffix to the text fields in questions of the "Array (Texts)" type.


  <option value="U">U</option>
[[File:suffix.png]]


  <option value="V">V</option>
Fig. 1: Suffix


  <option value="W">W</option>
[[File:prefix.png]]


  <option value="X">X</option>
Fig. 1: Prefix


  <option value="Y">Y</option>
SOLUTION
*Set up your survey to use JavaScript.
*Add the following script to the source of the array. Replace "12345" with the array question ID and modify the suffixes/prefixes as required.
*The function call must consist of a question ID followed by the suffixes for each column. Suffixes/Prefixes must be enclosed in quotes and comma separated.
*In this example question ID 12345 will get an "in &euro;" suffix in the first column and an "in %" in the next 3 columns.


  <option value="Z">Z</option>
<syntaxhighlight lang="php"><script type="text/javascript" charset="utf-8">


</select>
  $(document).ready(function() {


</td>
      // Call the function with the question ID followed by a comma-separated list of suffixes


</tr>
      addSuffix(12345, 'in &euro;', 'in %', 'in %', 'in %');


<tr>
      function addSuffix() {


<td>Your birth month</td>
          var qID = $(arguments)[0];


<td>
          // Assign some column-specific classes


<select id="field2">
          $('#question'+qID+' table.question tbody tr').each(function(i){


  <option value=""></option>
              $('td', this).each(function(i){


  <option value="01">January</option>
                  $(this).addClass('answerCol-'+(i+1)+'');


  <option value="02">February</option>
              });


  <option value="03">March</option>
          });


  <option value="04">April</option>
          // Some styling


  <option value="05">May</option>
          $('#question'+qID+' table.question tbody input[type="text"]').css({


  <option value="06">June</option>
              'width': '50%'


  <option value="07">July</option>
          });


  <option value="08">August</option>
          // Insert the suffixes


  <option value="09">September</option>
          $(arguments).each(function(i, val){


  <option value="10">October</option>
              if(i > 0) {


  <option value="11">November</option>
                  $('#question'+qID+' td.answerCol-'+i+' label').append(val);


  <option value="12">December</option>
              }


</select>
          });


<td>
      }


</tr>
  });


</table>
</script></syntaxhighlight>


<script type="text/javascript" src="http://crypto-js.googlecode.com/svn/tags/3.0.2/build/rollups/sha1.js"></script>
For adding a prefix you need to substitute the following line in the code


<script type="text/javascript">
<syntaxhighlight lang="php">$('#question'+qID+' td.answerCol-'+i+' label').append(val);</syntaxhighlight>


$(document).ready(function() {
with this


  $("#answer{SGQ}").attr('readonly', 'readonly');
<syntaxhighlight lang="php">$('#question'+qID+' td.answerCol-'+i+' label').prepend(val);</syntaxhighlight>


  //$("#answer{SGQ}").css('display', 'none'); //uncomment this line for not displaying the code
=Time question (Date question-like)=


});
''Tested with: 1.92+''


$('#field1, #field2').bind('change keyup', function() {
This workaround uses JavaScript/jQuery to build a Time question just like to the Date one.


  if ($("#field1").val()=='' || $("#field2").val()=='')
Implementation is as follows:
#Download the file at [http://trentrichardson.com/examples/timepicker/jquery-ui-timepicker-addon.js trentrichardson.com/examples/timepicker/jquery-ui-timepicker-addon.js] and upload it to your survey.
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
#Place the following code in the source of a short text question.


      $("#answer{SGQ}").val('');
<syntaxhighlight lang="javascript">
<script src="/upload/surveys/{SID}/jquery-ui-timepicker-addon.js"></script>


  else {
<script>


      var sha1 = CryptoJS.algo.SHA1.create();
$(document).ready(function(){


      sha1.update($("#field1").val());
$('#answer{SGQ}').timepicker({
timeText: 'Time', //here translation
hourText: 'hour', //here translation
minuteText: 'minute', //here translation
closeText: 'Done' //here translation
});


      sha1.update($("#field2").val());
$('#answer{SGQ}').attr('readonly', 'readonly');


      // sha1.update($("#otherfield").val());
$( "<style>"
+".ui-widget-header, .ui-datepicker-current { display:none; }"
+".ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; }"
+".ui-timepicker-div dl dd { margin: 0 10px 10px 65px; }"
+" }"
+"</style>").appendTo( "head" );


      var hash = sha1.finalize();
});


      $("#answer{SGQ}").val(hash);
</script></syntaxhighlight>


  }
=Calculating date difference=


});
''Tested with: 1.92+ Build 120909''


</script>
This workaround explains how calculate difference between two dates (in: days, weeks, months and years) and is based on this [http://ditio.net/2010/05/02/javascript-date-difference-calculation/ resource external].


</syntaxhighlight>
Requieres 2 groups and 'group by group' or 'question by question' presentation.


=Check validity of international phone number in subquestion=
Group 1 (2 date questions)
*1st question - Question code: "DD"
*2nd question - Question code: "D2"


''Tested with: 1.91+, Firefox 8, Chrome 15''
Group 2
*1 numerical input question.


As of version 1.92, you can use the core Validation querstion option to validate multiple short text and array text questions. Any question that fails that validation check will have its background color changed to red.
This question retrieves the number of days between the 2 dates, and check if D2 is posterior than DD.


Note, the below workaround will fail in version 1.92 because it contains a regular expression with curly braces.  However, as described above, support for this feature is now built-in to version 1.92
All the requiered code have to be added in the source of this numerical question.


This workaround uses javascript to check the validity of an international phone number in a subquestion of multiple short text.
<syntaxhighlight lang="php">


Add the following code to the source of your question.
<script type="text/javascript">


Replace "QQ" with the question ID and "NN" with the row number that you want to validate.
/*


<syntaxhighlight lang="php" enclose="div">
Adapted from http://ditio.net/2010/05/02/javascript-date-difference-calculation/
*/


<script type="text/javascript" charset="utf-8">
var DateDiff = {


   $(document).ready(function() {
   inDays: function(d1, d2) {


       // Call the phone function with the question ID and the row number
       var t2 = d2.getTime();


       phoneTest(QQ, NN);
       var t1 = d1.getTime();


       function phoneTest(qID, inputNum) {
       return parseInt[[t2-t1)/(24*3600*1000]];


          // Interrupt next/submit function
  },


          $('#movenextbtn, #movesubmitbtn').click(function(){
  inWeeks: function(d1, d2) {


              // Some vars - modify as required
      var t2 = d2.getTime();


              var phoneMatch = /^[0-9,+,(), ,]{1,}(,[0-9]+){0,}$/;
      var t1 = d1.getTime();


              var msg1 = 'Please enter a valid phone number.';
      return parseInt[[t2-t1)/(24*3600*1000*7]];


              // Test the input
  },


              var phoneInput = $('#question'+qID+' li:eq('+(inputNum-1)+') input.text').val();
  inMonths: function(d1, d2) {


              if(phoneInput != '' && !phoneMatch.test(phoneInput)) {
      var d1Y = d1.getFullYear();


                  alert(msg1);
      var d2Y = d2.getFullYear();


                  $('#question'+qID+' li:eq('+(inputNum-1)+') input.text').css({ 'background':'pink' });
      var d1M = d1.getMonth();


                  return false;
      var d2M = d2.getMonth();


              }
      return (d2M+12*d2Y)-(d1M+12*d1Y);


              else {
  },


                  $('#question'+qID+' li:eq('+(inputNum-1)+') input.text').css({ 'background':'' });
  inYears: function(d1, d2) {


                  return true;
      return d2.getFullYear()-d1.getFullYear();


              }
  }


          });
}


      }
d1 = new Date({substr(DD,0,4)}, {substr(DD,5,2)}, {substr(DD,8,2)});


  });
d2 = new Date({substr(D2,0,4)}, {substr(D2,5,2)}, {substr(D2,8,2)});


</script>
// Displays result


</syntaxhighlight>
document.write("<br />Number of <b>days</b>: "+DateDiff.inDays(d1, d2));


The regex is from: [http://www.regexlib.com/REDetails.aspx?regexp_id=1136 regexlib.com blank]
document.write("<br />Number of <b>weeks</b>: "+DateDiff.inWeeks(d1, d2));


=Check validity of email address in subquestion=
document.write("<br />Number of <b>months</b>: "+DateDiff.inMonths(d1, d2));


'Tested with: 1.91+, Firefox 8, Chrome 15''
document.write("<br />Number of <b>years</b>: "+DateDiff.inYears(d1, d2));


<div class="simplebox">As of version 1.92, you can use the core Validation querstion option to validate multiple short text and array text questions. Any question that fails that validation check will have its background color changed to red.<br />Note, the below workaround will fail in version 1.92 because it contains a regular expression with curly braces.  However, as described above, support for this feature is now built-in to version 1.92</div>
// filled the numerical input with number of days (or nights)


This workaround uses javascript to check the validity of an email address in a subquestion of multiple short text.
var nDays = +DateDiff.inDays(d1, d2);


Add the following code to the source of your question.
    jQuery(document).ready(function() {


Replace "QQ" with the question ID and "NN" with the row number that you want to validate.
      $(".numeric:eq(0) input.text").val(nDays);


<syntaxhighlight lang="php" enclose="div">
              $(".numeric:eq(0) input.text").attr('readonly','readonly');


<script type="text/javascript" charset="utf-8">
// check if d2 > d1


  $(document).ready(function() {
              $('#movenextbtn').hide();


      // Call the email function with the question ID and the row number
              $('#movesubmitbtn').hide();


      emailTest(QQ, NN);
              if(nDays >= 0){


       function emailTest(qID, inputNum) {
       // automatic submit (uncomment if needed)


          // Interrupt next/submit function
              // document.limesurvey.submit();


          $('#movenextbtn, #movesubmitbtn').click(function(){
              // $('body').hide();


              // Some vars - modify as required
                }


               var emailMatch = /^[a-zA-Z0-9\_\-]+[a-zA-Z0-9\.\_\-]*@([a-zA-Z0-9\_\-]+\.)+([a-zA-Z]{2,4}|travel|museum)$/;
               else alert("Check out date must be posterior than Check in\nPlease return to the previous page and edit your answers");


              var msg1 = 'Please enter a valid email address.';
  });


              // Test the input
</script>


              var emailInput = $('#question'+qID+' li:eq('+(inputNum-1)+') input.text').val();
</syntaxhighlight>


              if(emailInput != '' && !emailMatch.test(emailInput)) {
=How to set minDate/maxDate based on other Datequestions=


                  alert(msg1);
'' tested with Version 2.00+ Build 130708''


                  $('#question'+qID+' li:eq('+(inputNum-1)+') input.text').css({ 'background':'pink' });
QQ1 and QQ2 are the question identifiers. QQ2 gets as maxDate today and as minDate the date set in QQ1. The onClose event is needed because on document.ready the value of QQ1 is not yet known, although the value of today is.


                  return false;
<syntaxhighlight lang="php">
<script type="text/javascript" charset="utf-8">
    $(document).ready(function () {
        'use strict';
        var qID1 = QQ1,
            qID2 = QQ2,
            q1 = $('#question' + 'QQ1' + ' .popupdate'),
            q2 = $('#question' + 'QQ2' + ' .popupdate');
        //if q1 loses focus, its value is taken and used as minDate in q2 
        q1.datepicker("option", "onClose", function (datum){
            q2.datepicker( "option", "minDate", datum );
        });
        //q2 gets today as its maxDate
        q2.datepicker( 'option', 'maxDate', new Date() );
});
</script>
</syntaxhighlight>


              }
=Remove Question Help if empty=


              else {
''Version 2.00+ Build 130513''


                  $('#question'+qID+' li:eq('+(inputNum-1)+') input.text').css({ 'background':'' });
Change your question.pstpl in your Template Editor so that the line looks like below. I've added the ID tag help_{question_number}.


                  return true;
<syntaxhighlight lang="javascript">
<tr><td class="survey-question-help" id=help_{QUESTION_NUMBER}>{QUESTIONHELP}</td></tr>
</syntaxhighlight>


              }
You can then add the following javascript at the end of question.pstpl. If the contents of the help tag is empty then hide the element.
 
          });
 
      }
 
  });


<syntaxhighlight lang="javascript">
<script>
$(document).ready(function(){
  if($('#help_{QUESTION_NUMBER}').is(':empty')){
  $('#help_{QUESTION_NUMBER}').hide();
  }
});
</script>
</script>
</syntaxhighlight>
</syntaxhighlight>


The regex is from: [http://www.regexlib.com/REDetails.aspx?regexp_id=1653 regexlib.com blank]
= Implicit Association Test (IAT)=
== Version 2.06==


=Hiding the help-text when it's empty=
''Tested with: LimeSurvey version 2.06''


Tested with: 1.91+
This workaround uses JavaScript to insert an IAT interface into an Array (Texts) question. The workaround prompts respondents to assign words or phrases using the "E" or "I" keys. In mobile devices, "E" and "I" buttons are provided.<br /><br />


I had made a style in my template putting a border around my help-text. To avoid empty help-text to show up (and have lines instead of just 'nothing' when there was no help text, I used this piece of code in question.pstpl:
[[File:Iat_1.png]]<br /><br />


<syntaxhighlight lang="php" enclose="div">
Download a [[Media:Demo_IAT_Concept.zip|working template]] and [[Media:Demo_iat_concept.lss|working survey]].


<script type="text/javascript">
Implementation is as follows:


                  var questionhelp="{QUESTIONHELP}";
1) [[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].


                  if (questionhelp=="") {
2) Create your Array (Texts) with subquestions as follows:<br />
- Y scale - all of your "attribut" words or phrases<br />
- X scale - "Key Pressed" and "Elapsed Time (ms)"<br />
[[File:Iat_2.jpg]]


                          document.write("");}
3) Insert your association terms and the other text elements for the interface into the Question Help section as depicted below. The text strings must be separated with "double pipes" (||) and you will need to place them in the following order:<br />
- Left association term<br />
- Right association term<br />
- Instructional text<br />
- Finished text<br />
[[File:Iat_3.png]]


                  else {
4) Add the following to the end of template.js:<syntaxhighlight lang="javascript">
 
// A plugin to insert an IAT interface
                  document.write('<tr><td class="survey-question-help">'+ questionhelp +'</td></tr>');}
function implicitAssociation(el, showAnswers) {
 
              </script>
$(el).each(function() {
 
</syntaxhighlight>
var thisQuestion = $(this);
 
var thisQuestionHelp = $('img[src$="help.gif"]', thisQuestion).parent();
it can probably be optimized but it worked for me
var thisQuestionAnswers = $('table.question', thisQuestion).parent();
 
var startTime = '';
=Disable or hide selected fields in a matrix question=
var endTime = '';
 
var agent = navigator.userAgent.toLowerCase();
''Tested with: 1.92+, IE 7/8/9, FireFox 12/13''
var mobile = false;
 
if( /android|webos|iphone|ipad|ipod|blackberry/i.test(navigator.userAgent) ) { // Mobile devices
TASK
mobile = true;
 
}
Hide or disable selected fields in a Matrix, so that users cannot make any input.
 
// Some classes
(See example, where users should not make input into red framed fields)
$(thisQuestion).addClass('iatQuestion');
 
$('.subquestion-list').addClass('unanswered');
[[File:snap035.gif]]
 
// Hide the question help element
Fig. 1: Prevent users from input into first two fields (as an example)
$(thisQuestionHelp).hide();
 
SOLUTION
// Hide the answers
 
if(showAnswers != true) {
First find the name of the fields you want to hide / disable. In my case I used Firebug with firefox to figure that out.
$('table.question', thisQuestion).hide();
 
}
[[File:snap037.gif]]
 
// Insert IAT display
Fig. 2: Figure out the name(s) of the field(s)
var iatTexts = $(thisQuestionHelp).text().split('||');
 
var iatDisplayHTML = '<div class="iatWrapper">\
Then put the following script-code into the HTML-source code as described above:
<div class="iatLeftLabel">'+iatTexts[0]+'</div>\
 
<div class="iatRightLabel">'+iatTexts[1]+'</div>\
Note: you have to change the code to match the names of your fields!
<div class="iatWord"></div>\
 
<div class="iatInstructions">'+iatTexts[2]+'</div>\
<syntaxhighlight lang="javascript" enclose="div">
</div>\
 
<div class="iatMobileButtonWrapper">\
<script type="text/javascript" charset="utf-8">
<div class="iatButton iatLeftButton">E</div>\
 
<div class="iatButton iatRightButton">I</div>\
  $(document).ready(function() {
<div style="width:100%; clear:both;"></div>\
 
</div>';
      $('input[name="69965X2X10SQ001_SQ001"]').attr('disabled', 'disabled');
$(thisQuestionAnswers).prepend(iatDisplayHTML);
 
      $('input[name="69965X2X10SQ001_SQ002"]').attr('hidden', 'hidden');
// Show a word
 
function iatShowWord() {
  });
$('div.iatWord', thisQuestion).text($('.subquestion-list.unanswered:first .answertext', thisQuestion).text());
 
startTime = new Date();
</script>
 
$(document).bind('keypress.iatKeypress', function(e) {
</syntaxhighlight>
if(e.which == 101 || e.which == 105) {
 
var thisRow = $('.subquestion-list.unanswered:eq(0)', thisQuestion);
[[File:snap039.gif]]
$(thisRow).removeClass('unanswered');
 
endTime = new Date();
Fig. 3: Result: first field is diabled ("greyed out"), second one is completely hidden
$('input[type="text"]:eq(1)', thisRow).val(endTime.valueOf() - startTime.valueOf());
 
if(e.which == 101) {
I think this way one can apply many other attributes to a field like color etc. But did not try that myself.
$('input[type="text"]:eq(0)', thisRow).val('E');
 
}
Thanks to roB2009 for the input in the forum!
else {
 
$('input[type="text"]:eq(0)', thisRow).val('I');
=Add prefix or suffix to question type "Array (Texts)"=
}
 
$(document).unbind('keypress.iatKeypress');
TASK
if($('.subquestion-list.unanswered', thisQuestion).length > 0) {
 
iatShowWord();
Add a prefix or suffix to the text fields in questions of the "Array (Texts)" type.
}
 
else {
[[File:suffix.png]]
$('.iatLeftLabel, .iatWord, .iatRightLabel, .iatInstructions', thisQuestion).fadeOut('slow', function() {
 
$('div.iatWord', thisQuestion).text(iatTexts[3]);
Fig. 1: Suffix
$('.iatWord', thisQuestion).addClass('done').fadeIn('slow');
 
});
[[File:prefix.png]]
}
 
}
Fig. 1: Prefix
});
 
}
SOLUTION
function iatShowWordMobile() {
*Set up your survey to use JavaScript.
$('div.iatWord', thisQuestion).text($('.subquestion-list.unanswered:first .answertext', thisQuestion).text());
*Add the following script to the source of the array. Replace "12345" with the array question ID and modify the suffixes/prefixes as required.
startTime = new Date();
*The function call must consist of a question ID followed by the suffixes for each column. Suffixes/Prefixes must be enclosed in quotes and comma separated.
*In this example question ID 12345 will get an "in &euro;" suffix in the first column and an "in %" in the next 3 columns.
$('.iatButton', thisQuestion).bind('click.iatButtonClick', function(e) {
 
var thisRow = $('.subquestion-list.unanswered:eq(0)', thisQuestion);
<syntaxhighlight lang="php" enclose="div"><script type="text/javascript" charset="utf-8">
$(thisRow).removeClass('unanswered');
 
endTime = new Date();
  $(document).ready(function() {
$('input[type="text"]:eq(1)', thisRow).val(endTime.valueOf() - startTime.valueOf());
 
$('input[type="text"]:eq(0)', thisRow).val($(this).text());
      // Call the function with the question ID followed by a comma-separated list of suffixes
$('.iatButton', thisQuestion).unbind('click.iatButtonClick');
 
if($('.subquestion-list.unanswered', thisQuestion).length > 0) {
      addSuffix(12345, 'in &euro;', 'in %', 'in %', 'in %');
iatShowWordMobile();
 
}
      function addSuffix() {
else {
 
$('.iatLeftLabel, .iatWord, .iatRightLabel, .iatInstructions, .iatMobileButtonWrapper', thisQuestion).fadeOut('slow', function() {
          var qID = $(arguments)[0];
$('div.iatWord', thisQuestion).text(iatTexts[3]);
$('.iatWord', thisQuestion).addClass('done').fadeIn('slow');
});
}
});
}
// Start the IAT display
if(mobile == true) { // Mobile devices
$('.iatMobileButtonWrapper', thisQuestion).show();
$('.iatButton', thisQuestion).bind('click.iatStartMobile', function(e) {
$('.iatButton', thisQuestion).unbind('click.iatStartMobile');
iatShowWordMobile();
});
}
else {
$(document).bind('keypress.iatStart', function(e) { // Non-mobile devices
if(e.which == 101 || e.which == 105) {
$(document).unbind('keypress.iatStart');
iatShowWord();
}
});
}
});
} </syntaxhighlight>


          // Assign some column-specific classes
<br />5) Add the following to the end of template.css:<syntaxhighlight lang="css">
/* IAT questions */


          $('#question'+qID+' table.question tbody tr').each(function(i){
.iatWrapper {
position: relative;
width: 600px;
height: 300px;
color: #FFFFFF;
background-color: #000000;
font-size: 21px;
}


              $('td', this).each(function(i){
.iatLeftLabel {
float: left;
padding: 10px;
}


                  $(this).addClass('answerCol-'+(i+1)+'');
.iatRightLabel {
float: right;
padding: 10px;
}


              });
.iatWord {
position: absolute;
width: 100%;
top: 35%;
text-align: center;
font-size: 36px;
color:#0F0;
}


          });
.iatWord.done {
color:#FFFFFF;
}


          // Some styling
.iatInstructions {
position: absolute;
width: 100%;
bottom: 0px;
padding-bottom: 10px;
text-align: center;
}


          $('#question'+qID+' table.question tbody input[type="text"]').css({
.iatMobileButtonWrapper {
display: none;
position: relative;
width: 600px;
font-size: 36px;
}


              'width': '50%'
.iatButton {
 
float: left;
          });
margin: 20px;
 
width: 100px;
          // Insert the suffixes
padding: 2px 0 5px 0;
 
border: 5px solid #999;
          $(arguments).each(function(i, val){
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
-khtml-border-radius: 10px;
border-radius: 10px;
text-align: center;
line-height: normal;
cursor: pointer;
}


              if(i > 0) {
.iatLeftButton {
float: left;
}


                  $('#question'+qID+' td.answerCol-'+i+' label').append(val);
.iatRightButton {
 
float: right;
              }
}</syntaxhighlight>
 
          });
 
      }
 
  });


<br />6) To apply the plugin, add one of the two scripts below to the question source:<br />
- With hidden answer table (for normally activated survey)
<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">
$(document).ready(function() {
// Apply the IAT plugin to this question
implicitAssociation('#question{QID}');
});
</script></syntaxhighlight>
- With visible answer table (for testing purposes)
<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">
$(document).ready(function() {
// Apply the IAT plugin to this question
implicitAssociation('#question{QID}', true);
});
</script></syntaxhighlight>
</script></syntaxhighlight>


For adding a prefix you need to substitute the following line in the code
== Version 2.50==


<syntaxhighlight lang="php" enclose="div">$('#question'+qID+' td.answerCol-'+i+' label').append(val);</syntaxhighlight>
''Tested with: LimeSurvey version 2.50''


with this
This workaround uses JavaScript to insert an IAT interface into an Array (Texts) question. The workaround prompts respondents to assign words or phrases using the "E" or "I" keys. In mobile devices, "E" and "I" buttons are provided.<br /><br />


<syntaxhighlight lang="php" enclose="div">$('#question'+qID+' td.answerCol-'+i+' label').prepend(val);</syntaxhighlight>
[[File:Demo_iat_v250.png]]<br /><br />


=Time question (Date question-like)=
'''DOWNLOADS''':
*Template - [[Media:Demo_IAT_For_25_Template.zip|Demo_IAT_For_25_Template.zip]]
*Survey - [[Media:Demo_IAT_For_25_Survey.zip|Demo_IAT_For_25_Survey.zip]].


''Tested with: 1.92+''
'''IMPLEMENTATION''':
 
<ol>
This workaround uses JavaScript/jQuery to build a Time question just like to the Date one.
<li>[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].</li>
 
<li>Create your Array (Texts) with subquestions as follows:
Implementation is as follows:
<ul>
#Download the file at [http://trentrichardson.com/examples/timepicker/jquery-ui-timepicker-addon.js trentrichardson.com/examples/timepicker/jquery-ui-timepicker-addon.js] and upload it to your survey.
<li>Y scale - all of your "attribute" words or phrases</li>
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
<li>X scale - "Key Pressed" and "Elapsed Time (ms)"<br />[[File:Demo_iat_v250_2.png]]</li>
#Place the following code in the source of a short text question.
</ul>
 
</li>
<syntaxhighlight lang="javascript" enclose="div">
<li>Insert your association terms and the other text elements for the interface into the Question Help section as depicted below. The text strings must be separated with "double pipes" (||) and you will need to place them in the following order:
<script src="/upload/surveys/{SID}/jquery-ui-timepicker-addon.js"></script>
<ul>
 
<li>Left association term</li>
<script>
<li>Right association term</li>
 
<li>Instructional text</li>
$(document).ready(function(){
<li>Finished text<br />[[File:Demo_iat_v250_3.png]]</li>
 
</ul>
$('#answer{SGQ}').timepicker({
</li>
timeText: 'Time', //here translation
<li>Add the following to the end of template.js:<syntaxhighlight lang="javascript">// A plugin to insert an IAT interface
hourText: 'hour', //here translation
function implicitAssociation(el, showAnswers) {
minuteText: 'minute', //here translation
closeText: 'Done' //here translation
$(el).each(function() {
});
 
var thisQuestion = $(this);
$('#answer{SGQ}').attr('readonly', 'readonly');
var thisQuestionHelp = $('div.question-help', thisQuestion);
 
var thisQuestionAnswers = $('table.subquestion-list', thisQuestion).parent();
$( "<style>"
var startTime = '';
+".ui-widget-header, .ui-datepicker-current { display:none; }"
var endTime = '';
+".ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; }"
var agent = navigator.userAgent.toLowerCase();
+".ui-timepicker-div dl dd { margin: 0 10px 10px 65px; }"
var mobile = false;
+" }"
if( /android|webos|iphone|ipad|ipod|blackberry/i.test(navigator.userAgent) ) { // Mobile devices
+"</style>").appendTo( "head" );
mobile = true;
 
}
});
 
// Some classes
</script></syntaxhighlight>
$(thisQuestion).addClass('iat-question');
 
$('tr.subquestion-list', thisQuestion).addClass('unanswered');
=Calculating date difference=
 
// Hide the question help element
''Tested with: 1.92+ Build 120909''
$(thisQuestionHelp).hide();
 
$(thisQuestionHelp).closest('.question-help-container').hide();
This workaround explains how calculate difference between two dates (in: days, weeks, months and years) and is based on this [http://ditio.net/2010/05/02/javascript-date-difference-calculation/ resource external].
 
// Hide the answers
Requieres 2 groups and 'group by group' or 'question by question' presentation.
if(showAnswers != true) {
 
$('table.subquestion-list', thisQuestion).hide();
Group 1 (2 date questions)
}
*1st question - Question code: "DD"
*2nd question - Question code: "D2"
// Insert IAT display
 
var iatTexts = $(thisQuestionHelp).text().split('||');
Group 2
var iatDisplayHTML = '<div class="iatWrapper">\
*1 numerical input question.
<div class="iatLeftLabel">'+iatTexts[0]+'</div>\
 
<div class="iatRightLabel">'+iatTexts[1]+'</div>\
This question retrieves the number of days between the 2 dates, and check if D2 is posterior than DD.
<div class="iatWord"></div>\
 
<div class="iatInstructions">'+iatTexts[2]+'</div>\
All the requiered code have to be added in the source of this numerical question.
</div>\
 
<div class="iatMobileButtonWrapper">\
<syntaxhighlight lang="php" enclose="div">
<div class="iatButton iatLeftButton">E</div>\
 
<div class="iatButton iatRightButton">I</div>\
<script type="text/javascript">
<div style="width:100%; clear:both;"></div>\
 
</div>';
/*
$(thisQuestionAnswers).prepend(iatDisplayHTML);
 
Adapted from http://ditio.net/2010/05/02/javascript-date-difference-calculation/
// Show a word
*/
function iatShowWord() {
 
$('div.iatWord', thisQuestion).text($('tr.subquestion-list.unanswered:first .answertext', thisQuestion).text());
var DateDiff = {
startTime = new Date();
 
  inDays: function(d1, d2) {
$(document).bind('keypress.iatKeypress', function(e) {
 
if(e.which == 101 || e.which == 105) {
      var t2 = d2.getTime();
var thisRow = $('tr.subquestion-list.unanswered:eq(0)', thisQuestion);
$(thisRow).removeClass('unanswered');
endTime = new Date();
$('input[type="text"]:eq(1)', thisRow).val(endTime.valueOf() - startTime.valueOf());
if(e.which == 101) {
$('input[type="text"]:eq(0)', thisRow).val('E');
}
else {
$('input[type="text"]:eq(0)', thisRow).val('I');
}
$(document).unbind('keypress.iatKeypress');
if($('tr.subquestion-list.unanswered', thisQuestion).length > 0) {
iatShowWord();
}
else {
$('.iatLeftLabel, .iatWord, .iatRightLabel, .iatInstructions', thisQuestion).fadeOut('slow', function() {
$('div.iatWord', thisQuestion).text(iatTexts[3]);
$('.iatWord', thisQuestion).addClass('done').fadeIn('slow');
});
}
}
});
}
function iatShowWordMobile() {
$('div.iatWord', thisQuestion).text($('tr.subquestion-list.unanswered:first .answertext', thisQuestion).text());
startTime = new Date();
$('.iatButton', thisQuestion).bind('click.iatButtonClick', function(e) {
var thisRow = $('tr.subquestion-list.unanswered:eq(0)', thisQuestion);
$(thisRow).removeClass('unanswered');
endTime = new Date();
$('input[type="text"]:eq(1)', thisRow).val(endTime.valueOf() - startTime.valueOf());
$('input[type="text"]:eq(0)', thisRow).val($(this).text());
$('.iatButton', thisQuestion).unbind('click.iatButtonClick');
if($('tr.subquestion-list.unanswered', thisQuestion).length > 0) {
iatShowWordMobile();
}
else {
$('.iatLeftLabel, .iatWord, .iatRightLabel, .iatInstructions, .iatMobileButtonWrapper', thisQuestion).fadeOut('slow', function() {
$('div.iatWord', thisQuestion).text(iatTexts[3]);
$('.iatWord', thisQuestion).addClass('done').fadeIn('slow');
});
}
});
}
// Start the IAT display
if(mobile == true) { // Mobile devices
$('.iatMobileButtonWrapper', thisQuestion).show();
$('.iatButton', thisQuestion).bind('click.iatStartMobile', function(e) {
$('.iatButton', thisQuestion).unbind('click.iatStartMobile');
iatShowWordMobile();
});
}
else {
$(document).bind('keypress.iatStart', function(e) { // Non-mobile devices
if(e.which == 101 || e.which == 105) {
$(document).unbind('keypress.iatStart');
iatShowWord();
}
});
}
});
}</syntaxhighlight>
</li>
<li>Add the following to the end of template.css:<syntaxhighlight lang="css">/* IAT questions */


      var t1 = d1.getTime();
.iatWrapper {
position: relative;
width: 100%;
max-width: 600px;
height: 300px;
margin: 0 auto;
color: #D0DBE5;
background-color: #2C3E50;
font-size: 21px;
}


      return parseInt[[t2-t1)/(24*3600*1000]];
.iatLeftLabel {
float: left;
padding: 10px;
}


  },
.iatRightLabel {
float: right;
padding: 10px;
}


  inWeeks: function(d1, d2) {
.iatWord {
position: absolute;
width: 100%;
top: 35%;
text-align: center;
font-size: 36px;
color: #C4D1DE;
}


      var t2 = d2.getTime();
.iatWord.done {
color: #C4D1DE;
}


      var t1 = d1.getTime();
.iatInstructions {
position: absolute;
width: 100%;
bottom: 0px;
padding-bottom: 10px;
text-align: center;
}


      return parseInt[[t2-t1)/(24*3600*1000*7]];
.iatMobileButtonWrapper {
display: none;
position: relative;
width: 100%;
max-width: 600px;
margin: 0 auto;
font-size: 36px;
}


  },
.iatButton {
 
float: left;
  inMonths: function(d1, d2) {
margin: 20px;
 
width: 100px;
      var d1Y = d1.getFullYear();
padding: 2px 0 5px 0;
border: 5px solid #233140;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
-khtml-border-radius: 10px;
border-radius: 10px;
text-align: center;
line-height: normal;
cursor: pointer;
}


      var d2Y = d2.getFullYear();
.iatLeftButton {
float: left;
}


      var d1M = d1.getMonth();
.iatRightButton {
float: right;
}</syntaxhighlight>
</li>
<li>To apply the plugin, add one of the two scripts below to the question source:
<ul>
<li>With hidden answer table (for normally activated survey)<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">
$(document).ready(function() {
// Apply the IAT plugin to this question
implicitAssociation('#question{QID}');
});
</script></syntaxhighlight>
</li>
<li>With visible answer table (for testing purposes)
<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">
$(document).ready(function() {
// Apply the IAT plugin to this question
implicitAssociation('#question{QID}', true);
});
</script></syntaxhighlight>
</li>
</ul>
</li>
</ol>


      var d2M = d2.getMonth();
== Version 3.x ==


      return (d2M+12*d2Y)-(d1M+12*d1Y);
Here is a custom question theme for an Implicit Association Test (IAT) in version 3.x - https://github.com/tpartner/LimeSurvey-Implicit-Association-Test


  },
=Make multiple choice easier=


  inYears: function(d1, d2) {
''Tested with Version 2.00 in Chrome, FF, Safar, IE''


      return d2.getFullYear()-d1.getFullYear();
The problem:
We often have multiple choice question, where the user has to select 4 elements out of 78 possible answers or 3 out of 25 and so on.
If we use pen and paper version for this questions, we noticed that the participants go through the whole list and if an answer doesn't suit on the first sight, they cross it out.


  }
The solution:
The script gives the participant the possibility to cross out this answers in a digital way. The script adds a small "X" before the checkbox. When it is clicked, the label text of the checkbox turns grey.
(see the attached screenshot for an example)


}
Here is the script, just add it to help text
 
d1 = new Date({substr(DD,0,4)}, {substr(DD,5,2)}, {substr(DD,8,2)});
 
d2 = new Date({substr(D2,0,4)}, {substr(D2,5,2)}, {substr(D2,8,2)});
 
// Displays result
 
document.write("<br />Number of <b>days</b>: "+DateDiff.inDays(d1, d2));
 
document.write("<br />Number of <b>weeks</b>: "+DateDiff.inWeeks(d1, d2));
 
document.write("<br />Number of <b>months</b>: "+DateDiff.inMonths(d1, d2));
 
document.write("<br />Number of <b>years</b>: "+DateDiff.inYears(d1, d2));
 
// filled the numerical input with number of days (or nights)
 
var nDays = +DateDiff.inDays(d1, d2);
 
    jQuery(document).ready(function() {
 
      $(".numeric:eq(0) input.text").val(nDays);
 
              $(".numeric:eq(0) input.text").attr('readonly','readonly');
 
// check if d2 > d1
 
              $('#movenextbtn').hide();
 
              $('#movesubmitbtn').hide();
 
              if(nDays >= 0){
 
      // automatic submit (uncomment if needed)
 
              // document.limesurvey.submit();
 
              // $('body').hide();
 
                }
 
              else alert("Check out date must be posterior than Check in\nPlease return to the previous page and edit your answers");
 
  });
 
</script>


<syntaxhighlight lang="javascript">
<script type="text/javascript">
/**
* Small script that adds a "X" before a checkbox.
* On click the text color of the label of the checkbox turns grey
* If the user has to choose some answers from many options, he can "delete" answers, that on first sight looks wrong
* Add the script to the question in Limesurvey (for example within the source code of the help text)
*/
//Define the X (standard is a X in capitals)
var x = "X";
css_class = "x";
/*
* Define the style of the X
* Of course you can define the style in the stylesheet - if so, the script ignores the following settings
* If there is a style, than set style var to 1
*/
var style = 0;
var size = "12px";
var weight = "bold";
var color = "red";
var cursor = "pointer";
//element type - standard is a span element
var type = "span";
//Define the new font color of the checkbox label
label_color = "#bfbfbf";
//End of configuration - start of the function
//Define the element that is added
if(style == 1) {
ele = "<"+type+" class='"+css_class+"'>"+x+"</"+type+">";
}
else {
ele = "<"+type+" class='"+css_class+"' style='font-size:"+size+"; font-weight:"+weight+"; color:"+color+"; cursor:"+cursor+";'>"+x+"</"+type+">";
}
//Get the standard font-color of the used style
st_color = $("label").css("color");
//insert the X before the checkboxes
$(document).ready(function() {
$("li").each(function(){
$(this).prepend(ele);
});
});
//bind an eventhandler on the X (delegate is used, because Limesurvey uses an old jQuery version)
var css_class_c = "."+css_class;
$(document).delegate(css_class_c,"click",function() {
var color_check = $(this).siblings("label").data("color");
if(color_check != "true") {
    $(this).siblings("label").css("color",label_color).data("color","true");
    $(this).siblings('input').attr('checked', false);
}
else {
$(this).siblings("label").css("color",st_color).data("color","false");
}
});
$(".checkbox").click(function () {
$(this).siblings("label").css("color",st_color).data("color","false");
});
</script>
</syntaxhighlight>
</syntaxhighlight>


=How to set minDate/maxDate based on other Datequestions=


'' tested with Version 2.00+ Build 130708''
=Prevent the survey from being submitted when users press enter=
 
By default HTML forms are submitted when the user presses enter in any text field. This can be annoying with long survey pages or if your users are inexperienced with keyboard shortcuts.


QQ1 and QQ2 are the question identifiers. QQ2 gets as maxDate today and as minDate the date set in QQ1. The onClose event is needed because on document.ready the value of QQ1 is not yet known, although the value of today is.
To disable this behavior, add the following code snippet to template.js:


<syntaxhighlight lang="php" enclose="div">
<syntaxhighlight lang="javascript">$(document).ready(function(){
<script type="text/javascript" charset="utf-8">
    $('input').keypress(
    $(document).ready(function () {
    function(event){
        'use strict';
    if (event.which == '13') {
        var qID1 = QQ1,
        event.preventDefault();
            qID2 = QQ2,
      }
            q1 = $('#question' + 'QQ1' + ' .popupdate'),
    });
            q2 = $('#question' + 'QQ2' + ' .popupdate');
});</syntaxhighlight>
        //if q1 loses focus, its value is taken and used as minDate in q2 
        q1.datepicker("option", "onClose", function (datum){
            q2.datepicker( "option", "minDate", datum );
        });
        //q2 gets today as its maxDate
        q2.datepicker( 'option', 'maxDate', new Date() );
});
</script>
</syntaxhighlight>


=Remove Question Help if empty=
= Card Sorting=
== Version 2.06==


''Version 2.00+ Build 130513''
''Tested with: 2.06,''


Change your question.pstpl in your Template Editor so that the line looks like below. I've added the ID tag help_{question_number}.
This workaround uses JavaScript to allow dragging items into "buckets" to "card sort" them. A multiple-short-text question is used with subquestions being the draggable items. The data recorded for each item is the "bucket" number that it was dropped in. "Buckets" are pre-defined in a list in the "Help" section of the question.<br /><br />


<syntaxhighlight lang="javascript" enclose="div">
[[File:Card_sort_1.png]]<br /><br />
<tr><td class="survey-question-help" id=help_{QUESTION_NUMBER}>{QUESTIONHELP}</td></tr>
</syntaxhighlight>


You can then add the following javascript at the end of question.pstpl. If the contents of the help tag is empty then hide the element.
'''DOWNLOADS:'''<br />
- [[Media:Card_Sort.zip|Demo template]]<br />
- [[Media:Card_Sort.lss|Demo survey]].


<syntaxhighlight lang="javascript" enclose="div">
'''IMPLEMENTATION:'''
<script>
$(document).ready(function(){
  if($('#help_{QUESTION_NUMBER}').is(':empty')){
  $('#help_{QUESTION_NUMBER}').hide();
  }
});
</script>
</syntaxhighlight>


= Implicit Association Test (IAT)=
1) [[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].


''Tested with: 2.0, IE 7-10, Firefox, Chrome, iPad''
2) Create your multiple-short-text with subquestions.


This workaround uses JavaScript to insert an IAT interface into an Array (Texts) question. The workaround prompts respondents to assign words or phrases using the "E" or "I" keys. In mobile devices, "E" and "I" buttons are provided.<br /><br />
3) Insert the "bucket" labels into an unordered list in the Question Help section.


[[File:Iat_1.png]]<br /><br />
4) Add the following to the end of template.js:<syntaxhighlight lang="javascript">
 
function cardSort(qID) {
Download a [[Media:Demo_IAT_Concept.zip|working template]] and [[Media:Demo_iat_concept.lss|working survey]].
 
// Define some stuff...
Implementation is as follows:
var thisQuestion = $('#question'+qID);
 
var thisQuestionHelp = $('img[alt="Help"]', thisQuestion).parent();
1) [[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
thisQuestion.addClass('card-sort-question');
 
2) Create your Array (Texts) with sub-questions as follows:<br />
// Hide the "Help" section
- Y scale - all of your "attribut" words or phrases<br />
thisQuestionHelp.hide();
- X scale - "Key Pressed" and "Elapsed Time (ms)"<br />
[[File:Iat_2.jpg]]
//Insert the "card sort" elements
 
$('ul.questions-list', thisQuestion).parent().prepend('<div class="droppable items-start"></div><div class="items-end-wrapper" />');
3) Insert your association terms and the other text elements for the interface into the Question Help section as depicted below. The text strings must be separated with "double pipes" (||) and you will need to place them in the following order:<br />
$('ul:eq(0) li', thisQuestionHelp).each(function(i) {
- Left association term<br />
$('.items-end-wrapper', thisQuestion).append('<div class="items-end-inner">\
- Right association term<br />
<div class="items-end-text">'+$(this).html()+'</div>\
- Instructional text<br />
<div class="droppable items-end items-end-'+(i+1)+'" data-items-end="'+(i+1)+'"></div>\
- Finished text<br />
</div>')});
[[File:Iat_3.png]]
 
// Insert the "cards"
4) Add the following to the end of template.js:<syntaxhighlight lang="javascript" enclose="div">
$('li.answer-item', thisQuestion).each(function(i) {
// A plugin to insert an IAT interface
var thisSGQA = $(this).attr('id').replace(/javatbd/, '');
function implicitAssociation(el, showAnswers) {
var thisCode = $(this).attr('id').split('X'+qID)[1];
var thisHTML = $('label', this).html();
$(el).each(function() {
$('div.items-start').append('<div class="card draggable" data-sgqa="'+thisSGQA+'" data-code="'+thisCode+'">\
'+thisHTML+'\
var thisQuestion = $(this);
</div>');
var thisQuestionHelp = $('img[src$="help.gif"]', thisQuestion).parent();
});
var thisQuestionAnswers = $('table.question', thisQuestion).parent();
var startTime = '';
// Make the "cards" draggable
var endTime = '';
$('.draggable').draggable({
var agent = navigator.userAgent.toLowerCase();
revert: "invalid",
var mobile = false;
zIndex: 2700,
if( /android|webos|iphone|ipad|ipod|blackberry/i.test(navigator.userAgent) ) { // Mobile devices
helper: 'original',
mobile = true;
start: function( event, ui ) {
$(ui.helper).addClass('ui-draggable-helper');
},
stop: function( event, ui ) {
}
}
mobile = true;
});
// Some classes
// Set the targets for the draggables
$(thisQuestion).addClass('iatQuestion');
$('.droppable.items-start').droppable({
$('.subquestion-list').addClass('unanswered');
hoverClass: 'target-hover',
accept: '.draggable.moved'  
// Hide the question help element
});
$(thisQuestionHelp).hide();
$('.droppable.items-end').droppable({
hoverClass: 'target-hover',
// Hide the answers
accept: '.draggable'  
if(showAnswers != true) {
});
$('table.question', thisQuestion).hide();
}
// After dropped
$('.droppable').bind('drop', function(event, ui) {
// Insert IAT display
var iatTexts = $(thisQuestionHelp).text().split('||');
// Physically move the draggable to the target
var iatDisplayHTML = '<div class="iatWrapper">\
// (the plugin just visually moves it)
<div class="iatLeftLabel">'+iatTexts[0]+'</div>\
// (need to use a clone here to fake out iPad)
<div class="iatRightLabel">'+iatTexts[1]+'</div>\
var newDraggable = $(ui.draggable).clone();
<div class="iatWord"></div>\
$(newDraggable).appendTo(this);
<div class="iatInstructions">'+iatTexts[2]+'</div>\
$(ui.draggable).remove();
</div>\
if($(this).hasClass('items-end')) {
<div class="iatMobileButtonWrapper">\
$(newDraggable).addClass('moved');
<div class="iatButton iatLeftButton">E</div>\
}
<div class="iatButton iatRightButton">I</div>\
else {
<div style="width:100%; clear:both;"></div>\
$(newDraggable).removeClass('moved');
</div>';
}
$(thisQuestionAnswers).prepend(iatDisplayHTML);
$(newDraggable).removeClass('ui-draggable-helper ui-draggable-dragging').css({
'z-index':'',
// Show a word
'top':'auto',  
function iatShowWord() {
'left':'auto'
$('div.iatWord', thisQuestion).text($('.subquestion-list.unanswered:first .answertext', thisQuestion).text());
});
startTime = new Date();
// Now, make this new clone draggable
$(document).bind('keypress.iatKeypress', function(e) {
$(newDraggable).draggable({  
if(e.which == 101 || e.which == 105) {
revert: "invalid",  
var thisRow = $('.subquestion-list.unanswered:eq(0)', thisQuestion);
zIndex: 2700,
$(thisRow).removeClass('unanswered');
helper: 'original',
endTime = new Date();
start: function( event, ui ) {
$('input[type="text"]:eq(1)', thisRow).val(endTime.valueOf() - startTime.valueOf());
$(ui.helper).addClass('ui-draggable-helper');
if(e.which == 101) {
},
$('input[type="text"]:eq(0)', thisRow).val('E');
stop: function( event, ui ) {
}
}
else {
});
$('input[type="text"]:eq(0)', thisRow).val('I');
});
}
$(document).unbind('keypress.iatKeypress');
// Initial "card" positions
if($('.subquestion-list.unanswered', thisQuestion).length > 0) {
$('li.question-item input.text', thisQuestion).each(function(i) {
iatShowWord();
if($(this).val() > 0) {
}
$('.items-end[data-items-end="'+$(this).val()+'"]').append($('.card[data-sgqa="'+$(this).attr('name')+'"]'));
else {
$('.card[data-sgqa="'+$(this).attr('name')+'"]').appendTo($('.items-end[data-items-end="'+$(this).val()+'"]')).addClass('moved');
$('.iatLeftLabel, .iatWord, .iatRightLabel, .iatInstructions', thisQuestion).fadeOut('slow', function() {
$('div.iatWord', thisQuestion).text(iatTexts[3]);
$('.iatWord', thisQuestion).addClass('done').fadeIn('slow');
});
}
}
});
}
function iatShowWordMobile() {
$('div.iatWord', thisQuestion).text($('.subquestion-list.unanswered:first .answertext', thisQuestion).text());
startTime = new Date();
$('.iatButton', thisQuestion).bind('click.iatButtonClick', function(e) {
var thisRow = $('.subquestion-list.unanswered:eq(0)', thisQuestion);
$(thisRow).removeClass('unanswered');
endTime = new Date();
$('input[type="text"]:eq(1)', thisRow).val(endTime.valueOf() - startTime.valueOf());
$('input[type="text"]:eq(0)', thisRow).val($(this).text());
$('.iatButton', thisQuestion).unbind('click.iatButtonClick');
if($('.subquestion-list.unanswered', thisQuestion).length > 0) {
iatShowWordMobile();
}
else {
$('.iatLeftLabel, .iatWord, .iatRightLabel, .iatInstructions, .iatMobileButtonWrapper', thisQuestion).fadeOut('slow', function() {
$('div.iatWord', thisQuestion).text(iatTexts[3]);
$('.iatWord', thisQuestion).addClass('done').fadeIn('slow');
});
}
});
}
// Start the IAT display
if(mobile == true) { // Mobile devices
$('.iatMobileButtonWrapper', thisQuestion).show();
$('.iatButton', thisQuestion).bind('click.iatStartMobile', function(e) {
$('.iatButton', thisQuestion).unbind('click.iatStartMobile');
iatShowWordMobile();
});
}
else {
$(document).bind('keypress.iatStart', function(e) { // Non-mobile devices
if(e.which == 101 || e.which == 105) {
$(document).unbind('keypress.iatStart');
iatShowWord();
}
});
}
}
});
});
} </syntaxhighlight>
// Interrupt the Next/Submit function and load the inputs
$('form#limesurvey').submit(function(){
$('.question-item input.text', thisQuestion).val(0);
$('.droppable.items-end .card', thisQuestion).each(function(i) {
var thisItemsEnd = $(this).closest('.droppable.items-end').attr('data-items-end');
var thisID = $(this).attr('data-sgqa');
$('#answer'+thisID+'').val(thisItemsEnd);
});
});
}</syntaxhighlight>


<br />5) Add the following to the end of template.css:<syntaxhighlight lang="css" enclose="div">
<br />5) Add the following to the end of template.css:<syntaxhighlight lang="css">
/* IAT questions */
/* Card Sort */
 
.iatWrapper {
.card-sort-question ul.subquestions-list {
position: relative;
/*display: none;
width: 600px;
clear: both;*/
height: 300px;
color: #FFFFFF;
background-color: #000000;
font-size: 21px;
}
}
 
.iatLeftLabel {
.card-sort-question .items-start {
float: left;
float: left;
padding: 10px;
width: 340px;
height: 317px;
margin-top: 8px;
border: 1px solid #666;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
-khtml-border-radius: 6px;
border-radius: 6px;
}
}
 
.iatRightLabel {
.card-sort-question .items-start.target-hover {
float: right;
background:#C9C9C9;
padding: 10px;
}
}
 
.iatWord {
.card-sort-question .items-end-wrapper {
position: absolute;
float: left;
width: 100%;
margin-left: 20px;
top: 35%;
width: 340px;
text-align: center;
font-size: 36px;
color:#0F0;
}
}
 
.iatWord.done {
.card-sort-question .items-end {
color:#FFFFFF;
width: 338px;
min-height: 73px;
margin-bottom: 10px;
padding-bottom: 5px;
border: 1px solid #666;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
-khtml-border-radius: 6px;
border-radius: 6px;
background: #EBEBEB;
}
}
 
.iatInstructions {
.card-sort-question .items-end.target-hover {
position: absolute;
background: #C9C9C9;
width: 100%;
bottom: 0px;
padding-bottom: 10px;
text-align: center;
}
}
 
.iatMobileButtonWrapper {
.card-sort-question .items-end.ui-state-disabled {
display: none;
    opacity: 1;
position: relative;
filter:alpha(opacity=100);
width: 600px;
font-size: 36px;
}
}
 
.iatButton {
.card-sort-question .items-end-text {
float: left;
width: 338px;
margin: 20px;
padding-bottom: 5px;
width: 100px;
background: #FFFFFF;
padding: 2px 0 5px 0;
font-size: 110%;
border: 5px solid #999;
font-weight: bold;
-moz-border-radius: 10px;
}
-webkit-border-radius: 10px;
-khtml-border-radius: 10px;
.card-sort-question .card  {
border-radius: 10px;
display: inline-block;
width: 140px;
height: auto;
margin: 5px 8px;
padding: 3px;
    border: 2px solid #2F4354;
-moz-border-radius: 8px;
-webkit-border-radius: 8px;
-khtml-border-radius: 8px;
border-radius: 8px;
background-color: #43b3d1;
color: #2f4354;
font-weight: bold;
text-align: center;
text-align: center;
line-height: normal;
line-height: normal;
cursor: pointer;
}
}
 
.iatLeftButton {
.card-sort-question .items-end .card  {
float: left;
margin: 5px 7px;
}
}
 
.iatRightButton {
.card-sort-question  div.answer {
float: right;
clear: both;
padding-top: 1px;
margin-top: 0;
}</syntaxhighlight>
}</syntaxhighlight>


<br />6) To apply the plugin, add one of the two scripts below to the question source:<br />
<br />6) Add this to the source of the question to apply the function.<syntaxhighlight lang="javascript">
- With hidden answer table (for normally activated survey)
<script type="text/javascript" charset="utf-8">
<syntaxhighlight lang="javascript" enclose="div"><script type="text/javascript" charset="utf-8">
$(document).ready(function() {
$(document).ready(function() {
// Apply the IAT plugin to this question
cardSort({QID});
implicitAssociation('#question{QID}');
    });
});
</script></syntaxhighlight>
- With visible answer table (for testing purposes)
<syntaxhighlight lang="javascript" enclose="div"><script type="text/javascript" charset="utf-8">
$(document).ready(function() {
// Apply the IAT plugin to this question
implicitAssociation('#question{QID}', true);
});
</script></syntaxhighlight>
</script></syntaxhighlight>


== Version 3.x ==
Here is a custom question theme for card Sorting in version 3.x. - https://github.com/tpartner/LimeSurvey-Card-Sorting-3.x
== Version 4.x ==
Here is a custom question theme for card Sorting in version 4.x. - https://github.com/tpartner/LimeSurvey-Card-Sorting-4.x
= Q Sorting=
This workaround is heavily based on the Card Sorting above. It uses JavaScript to allow dragging items into "buckets" to "card sort" them with the additional twist to limit the cards per bucket. A multiple-short-text question is used with subquestions being the draggable items. The data recorded for each item is the "bucket" number that it was dropped in. "Buckets" and their limits are pre-defined in a list in the "Display" section of the question.<br /><br />
== Version 3.x==
''Tested with: 3.08 - 3.15''
Custom question theme - [[Media:QSort.zip]]<br />
== Version 4.x==
''Tested with: 4.5.1''
Custom question theme - [[Media:qSort-4.x.zip]]<br />
=Text Input (e.g. "short free text", "huge free text"): Force UpperCase + onBlur Trim=
Add this to the source of your target question to apply the function.


=Make multiple choice easier=
Here is the primary JavaScript/jQuery coding:
<syntaxhighlight lang="javascript">
<script type='text/javascript' charset='utf-8'>
$( document ).ready( function() {
var this_surveyId='{SID}';
var this_groupId='{self.gid}';
var this_quesitonId='{self.qid}';
var this_self='{self.sgqa}';


''Tested with Version 2.00 in Chrome, FF, Safar, IE''
// Remark for the variable 'my_targetTextBox':
// ------------------------------------
// For Text-type question (e.g. 'short free text', 'huge free text'), use : '#answer' + this_self;
// For 'Multiple Choice'-type question enabled with the 'Other' option which provides a textbox, use : '#answer' + this_self;
// For 'List (radio)'-type question enabled with the 'Other' option which provides a textbox, use : '#answer' + this_self + 'text'.
var my_targetTextBox='#answer' + this_self;


The problem:
We often have multiple choice question, where the user has to select 4 elements out of 78 possible answers or 3 out of 25 and so on.
If we use pen and paper version for this questions, we noticed that the participants go through the whole list and if an answer doesn't suit on the first sight, they cross it out.


The solution:
// Trim:
The script gives the participant the possibility to cross out this answers in a digital way. The script adds a small "X" before the checkbox. When it is clicked, the label text of the checkbox turns grey.
// -----
(see the attached screenshot for an example)
$( my_targetTextBox ).blur( function(){
$( this ).val( $.trim( $( this ).val() ) );
});


Here is the script, just add it to help text
// Uppercase:
// ----------
// Operate for its appearance only (but have no effect to modify its intrinsic value) - display as UpperCase:
$( my_targetTextBox ).css( 'text-transform', 'uppercase' );
// Operate for its intrinsic value:
$( my_targetTextBox ).blur( function() {
$( this ).val( $( this ).val().toUpperCase() );
});
});
</script>
</syntaxhighlight>


<syntaxhighlight lang="javascript" enclose="div">
For Text-type question (e.g. 'short free text', 'huge free text'), set the 'this_self' variable as below:
<script type="text/javascript">
<syntaxhighlight lang="javascript">
var my_targetTextBox='#answer' + this_self;
/**
</syntaxhighlight>
* Small script that adds a "X" before a checkbox.
 
* On click the text color of the label of the checkbox turns grey
For 'Multiple Choice'-type question enabled with the 'Other' option which provides a textbox, set the 'this_self' variable as below:
* If the user has to choose some answers from many options, he can "delete" answers, that on first sight looks wrong
<syntaxhighlight lang="javascript">
* Add the script to the question in Limesurvey (for example within the source code of the help text)
var my_targetTextBox='#answer' + this_self;
*/
</syntaxhighlight>
 
//Define the X (standard is a X in capitals)
 
var x = "X";
For 'List (radio)'-type question enabled with the 'Other' option which provides a textbox, set the 'this_self' variable as below:
css_class = "x";
<syntaxhighlight lang="javascript">
var my_targetTextBox='#answer' + this_self + 'text';
</syntaxhighlight>
 
= Convert arrays to dropdowns on smaller screens =
 
''Tested with: 2.05, IE 8-11, Firefox, Chrome''
 
Here's a little jQuery plugin that converts arrays to drop-downs on smaller screens.<br /><br />
 
'''IMPLEMENTATION:'''
 
1) Add the following to the end of template.js:<syntaxhighlight lang="javascript">
$(document).ready(function(){
    // Apply the responsiveArray plugin with default settings
    $('.array-10-pt, .array-flexible-row, .array-5-pt, .array-increase-same-decrease, .array-yes-uncertain-no').responsiveArray();
});
   
   
/*
/*****  
* Define the style of the X
    A plugin to insert drop-downs into arrays for small screens
* Of course you can define the style in the stylesheet - if so, the script ignores the following settings
    Copyright (C) 2015 - Tony Partner (http://partnersurveys.com)
* If there is a style, than set style var to 1
    Licensed MIT, GPL
*/
    Version - 2.0
var style = 0;
    Create date - 09/04/15
var size = "12px";
*****/
var weight = "bold";
(function( $ ){
var color = "red";
    $.fn.responsiveArray = function(options) {
var cursor = "pointer";
   
        var opts = $.extend( {
            chooseText: 'Please choose...'           
        }, options);
   
   
//element type - standard is a span element
        return this.each(function() {
var type = "span";
   
   
//Define the new font color of the checkbox label
            var thisQuestion = $(this);
label_color = "#bfbfbf";
            var thisQID = $(thisQuestion).attr('id').split('question')[1];
 
            // Some classes
            $(thisQuestion).addClass('responsive-array');
            $('table.questions-list tr', thisQuestion).each(function(i){
                $('> *', this).each(function(i){
                    $(this).addClass('col-'+i);
                    if(i != 0) {
                        $(this).addClass('expanded');
                    }
                });
            });
 
            // Insert a column
            $('.col-0', thisQuestion).after('<td class="dropdown-cell" />');
 
            // Insert dropdowns
            $('body').append('<select style="display:none;" class="responsive-select responsive-select-'+thisQID+'" />');
            $('table.questions-list thead th.expanded', thisQuestion).each(function(i){
                $('.responsive-select-'+thisQID).append('<option value="">'+$(this).text()+'</option>');
            });
            $('table.questions-list tbody .dropdown-cell', thisQuestion).append($('.responsive-select-'+thisQID+'').clone());
            $('tr.radio-list', thisQuestion).each(function(i){
                var thisRow = $(this);
                $('input.radio', this).each(function(i){
                    $('.responsive-select-'+thisQID+' option:eq('+i+')', thisRow).attr('value', $(this).attr('id'));
                });                   
                if($('input.radio:checked', thisRow).length > 0) {
                    $('.responsive-select-'+thisQID+'', thisRow).val($('input.radio:checked', thisRow).attr('id'));
                }
                else {
                    $('.responsive-select-'+thisQID+'', thisRow).prepend('<option value="" selected="selected">'+opts.chooseText+'</option>');
                }
            });
            $('.responsive-select-'+thisQID+'', thisQuestion).show();
 
            // Listeners on radios
            $('input.radio', thisQuestion).click(function(event) {
                var thisRow = $(this).closest('tr');
                var thisID = $(this).attr('id');
                //$('option[value="'+thisID+'"]').attr('selected', 'selected');
                $('.responsive-select', thisRow).val(thisID);
                $('.responsive-select option[value=""]', thisRow).remove();
            });
 
            // Listeners on dropdowns
            $('.responsive-select-'+thisQID+'').change(function() {
                $('#'+$(this).val()+'').click();
            });
   
   
        });
    };
})( jQuery );</syntaxhighlight>
<br />2) Add something like this to template.css (I have it set to switch at 640px but adjust that as you see fit):<syntaxhighlight lang="css">
/*****
    Styles for the responsiveArray plugin
    Copyright (C) 2015 - Tony Partner (http://partnersurveys.com)
    Licensed MIT, GPL
    Version - 2.0
    Create date - 09/04/15
*****/
   
   
//End of configuration - start of the function
.responsive-array .dropdown-cell {
    display: none;
    text-align: left;
    padding-left: 8px;
}
   
   
//Define the element that is added
.responsive-array .dropdown-cell select {
if(style == 1) {
        max-width: none;
ele = "<"+type+" class='"+css_class+"'>"+x+"</"+type+">";
}
}
else {
ele = "<"+type+" class='"+css_class+"' style='font-size:"+size+"; font-weight:"+weight+"; color:"+color+"; cursor:"+cursor+";'>"+x+"</"+type+">";
@media screen and (max-width: 640px) {
}
    .responsive-array table.question {
        table-layout: auto;
//Get the standard font-color of the used style
    }
st_color = $("label").css("color");
    .responsive-array table.question .col-0,
    .responsive-array table.question .dropdown-cell {
//insert the X before the checkboxes
        width: 50%;
$(document).ready(function() {
    }
$("li").each(function(){
    .responsive-array .dropdown-cell {
$(this).prepend(ele);
        display: table-cell;
});
    }
});
    .responsive-array th.expanded,
    .responsive-array td.expanded {
//bind an eventhandler on the X (delegate is used, because Limesurvey uses an old jQuery version)
        display: none;
var css_class_c = "."+css_class;
    }
$(document).delegate(css_class_c,"click",function() {
}</syntaxhighlight>
var color_check = $(this).siblings("label").data("color");
if(color_check != "true") {
    $(this).siblings("label").css("color",label_color).data("color","true");
    $(this).siblings('input').attr('checked', false);
}
else {
$(this).siblings("label").css("color",st_color).data("color","false");
}
});
$(".checkbox").click(function () {
$(this).siblings("label").css("color",st_color).data("color","false");
});
</script>
</syntaxhighlight>


<br />Note that the "Please choose" text is an optional setting. To change it, apply the plugin like this:<syntaxhighlight lang="javascript">
$(document).ready(function(){
    // Apply the responsiveArray plugin with default settings
    $('.array-10-pt, .array-flexible-row, .array-5-pt, .array-increase-same-decrease, .array-yes-uncertain-no').responsiveArray({
        chooseText: 'Select one...'
    });
});</syntaxhighlight>


=Prevent the survey from being submitted when users press enter=
= Van Westendorp Pricing Sliders=
== Version 2.06==


By default HTML forms are submitted when the user presses enter in any text field. This can be annoying with long survey pages or if your users are inexperienced with keyboard shortcuts.
''Tested with: LimeSurvey version 2.06''


To disable this behavior, add the following code snippet to template.js:
This workaround uses JavaScript to dynamically control sliders to facilitate the [http://www.5circles.com/van-westendorp-pricing-the-price-sensitivity-meter/ Van Westendorp pricing research methodology]. When a slider is manipulated, all of the "higher" sliders are dynamically pushed to higher levels and "lower" sliders are pushed to lower levels. This ensures that the data will always be valid.<br /><br />


<syntaxhighlight lang="javascript" enclose="div">$(document).ready(function(){
[[File:Test_Van_Westendorp_Pricing_206_v2.png]]<br /><br />
    $('input').keypress(
    function(event){
    if (event.which == '13') {
        event.preventDefault();
      }
    });
});</syntaxhighlight>


= Card Sorting=
'''Download''' a [[Media:Test_Van_Westendorp_Pricing_206.zip|working template and survey]] (un-zip the package and install the template before the survey).


''Tested with: 2.05, IE 8-11, Firefox, Chrome, iPad''
'''Implementation''':


This workaround uses JavaScript to allow dragging items into "buckets" to "card sort" them. A multiple-short-text question is used with sub-questions being the draggable items. The data recorded for each item is the "bucket" number that it was dropped in. "Buckets" are pre-defined in a list in the "Help" section of the question.<br /><br />
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
#Create your Multiple Numeric question with subquestions as seen in the image above.
#Add the following to the end of template.js:<syntaxhighlight lang="javascript">function shuffleArray(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}


[[File:Card_sort_1.png]]<br /><br />
/*****
    A jQuery plugin to facilitate the Van Westendorp pricing research methodology
    Copyright (C) 2016 - Tony Partner (http://partnersurveys.com)
    Licensed MIT, GPL
    Version - 2.0
    Create date - 19/10/16
*****/
(function( $ ){


'''DOWNLOADS:'''<br />
$.fn.vWPricing = function(options) { 
- [[Media:Card_Sort.zip|Demo template]]<br />
- [[Media:Card_Sort.lss|Demo survey]].
var opts = $.extend( {
order: [1, 2, 3, 4],
randomOrder: false
}, options);
return this.each(function() {
var thisQuestion = $(this);
thisQuestion.addClass('vwp-question');
// Add some attributes and set the slider max/min values
var itemsLength = $('li.question-item', thisQuestion).length;
$('li.question-item', thisQuestion).each(function(i) {
$(this).attr('data-item', (i+1));
$('.ui-slider', this).attr('data-slider', (i+1));
var thisSliderMin = $('.ui-slider', this).slider('option', 'min');
$('.ui-slider', this).slider('option', 'min', thisSliderMin+(i));
var thisSliderMax = $('.ui-slider', this).slider('option', 'max');
$('.ui-slider', this).slider('option', 'max', thisSliderMax-((itemsLength-1)-i));
});
// Slider order
if(opts.randomOrder == true) {
shuffleArray(opts.order);
}
$(opts.order).each(function(i, val) {
$('ul.subquestions-list', thisQuestion).append($('li.question-item[data-item="'+val+'"]', thisQuestion));
});
// Listeners on the sliders
$('.ui-slider', thisQuestion).on('slide', function(event, ui) {
handleVwpSliders(this, ui.value);
});
$('.ui-slider', thisQuestion).on('slidestop', function(event, ui) {
handleVwpSliders(this, ui.value);
});
// A function to handle the siders
function handleVwpSliders(el, sliderVal) {
var thisRow = $(el).closest('li');
var movedIndexNum = $(el).attr('data-slider');
$('input.text', thisRow).val(sliderVal);
$('.slider-callout', thisRow).html($('input.text', thisRow).val());
var lowerSliders = $('.ui-slider', thisQuestion).filter(function() {
return $(this).attr('data-slider') < movedIndexNum;
});
var higherSliders = $('.ui-slider', thisQuestion).filter(function() {
return $(this).attr('data-slider') > movedIndexNum;
});
$(lowerSliders).each(function(i) {
var thisIndexNum = $(this).attr('data-slider');
var newSliderVal = sliderVal-(movedIndexNum-thisIndexNum);
if($(this).slider('option', 'value') > newSliderVal) {
var thisRow = $(this).closest('li');
$(this).slider('option', 'value', newSliderVal);
$('input.text', thisRow).val(newSliderVal);
$('.slider-callout', thisRow).html($('input.text', thisRow).val());
}
});
$(higherSliders).each(function(i) {
var thisIndexNum = $(this).attr('data-slider');
var newSliderVal = sliderVal+(thisIndexNum-movedIndexNum);
if($(this).slider('option', 'value') < newSliderVal) {
var thisRow = $(this).closest('li');
$(this).slider('option', 'value', newSliderVal);
$('input.text', thisRow).val(newSliderVal);
$('.slider-callout', thisRow).html($('input.text', thisRow).val());
}
});
}
});
};
})( jQuery );</syntaxhighlight>
#Add the following to the end of template.css (this is for a copy of [http://extensions.sondages.pro/skeletonquest-rwd-for-limesurvey/ Denis Chenu's SkeletonQuest template]):<syntaxhighlight lang="css">/**** Van Westendorp Pricing ****/


'''IMPLEMENTATION:'''
.vwp-question ul.subquestions-list,
.vwp-question ul.subquestions-list li {
    display:block;
}


1) [[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
.vwp-question ul.subquestions-list li {
margin-bottom: 15px;
}
 
.vwp-question ul.subquestions-list .slider-label {
    display:block;
    padding: 0.3em 0.5em 0.7em;
    width: auto;
max-width: 100%;
text-align: left;
}
 
.vwp-question .slider-element {
    display: block;
margin-left: 2%;
width:90%;
max-width: 32em;
}</syntaxhighlight>
#To apply the plugin, add this script the multiple-numeric question source.
There are two options to adjust<br /> -'''order''' - this is the order that your subquestions will be displayed (default, 1, 2, 3, 4)<br /> -'''randomOrder''' - if set to true, the '''order''' option will be overridden and the subquestion order will be randomized (default, false)<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">
$(document).ready(function(){
$('#question{QID}').vWPricing({
order: [1, 2, 3, 4],
randomOrder: false
});
});
</script></syntaxhighlight>
== Version 2.5x==
 
''Tested with: LimeSurvey version 2.54.5''
 
This workaround uses JavaScript to dynamically control sliders to facilitate the [http://www.5circles.com/van-westendorp-pricing-the-price-sensitivity-meter/ Van Westendorp pricing research methodology]. When a slider is manipulated, all of the "higher" sliders are dynamically pushed to higher levels and "lower" sliders are pushed to lower levels. This ensures that the data will always be valid.<br /><br />
 
[[File:Test_Van_Westendorp_Pricing_25x.png]]<br /><br />
 
'''Download''' a [[Media:Test_Van_Westendorp_Pricing_25x.zip|working template and survey]] (un-zip the package and install the template before the survey).


2) Create your multiple-short-text with sub-questions.
'''Implementation''':


3) Insert the "bucket" labels into an unordered list in the Question Help section.
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
#Create your Multiple Numeric question with subquestions as seen in the image above.
#Add the following to the end of template.js:<syntaxhighlight lang="javascript">function shuffleArray(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
 
/*****
    A jQuery plugin to facilitate the Van Westendorp pricing research methodology
    Copyright (C) 2016 - Tony Partner (http://partnersurveys.com)
    Licensed MIT, GPL
    Version - 2.0
    Create date - 19/10/16
*****/
(function( $ ){


4) Add the following to the end of template.js:<syntaxhighlight lang="javascript" enclose="div">
$.fn.vWPricing25 = function(options)
function cardSort(qID) {
var opts = $.extend( {
// Define some stuff...
order: [1, 2, 3, 4],
var thisQuestion = $('#question'+qID);
randomOrder: false
var thisQuestionHelp = $('img[alt="Help"]', thisQuestion).parent();
}, options);
thisQuestion.addClass('card-sort-question');
return this.each(function() {  
// Hide the "Help" section
thisQuestionHelp.hide();
var thisQuestion = $(this);
//Insert the "card sort" elements
thisQuestion.addClass('vwp-question');
$('ul.questions-list', thisQuestion).parent().prepend('<div class="droppable items-start"></div><div class="items-end-wrapper" />');
$('ul:eq(0) li', thisQuestionHelp).each(function(i) {
// Add some attributes and set the slider max/min values
$('.items-end-wrapper', thisQuestion).append('<div class="items-end-inner">\
var itemsLength = $('.question-item', thisQuestion).length;
<div class="items-end-text">'+$(this).html()+'</div>\
$('.question-item', thisQuestion).each(function(i) {
<div class="droppable items-end items-end-'+(i+1)+'" data-items-end="'+(i+1)+'"></div>\
var thisInput = $('input[type=text]', this);
</div>')});
var thisVal = $(thisInput).val();
   
// Insert the "cards"
$(this).attr('data-item', (i+1));
$('li.answer-item', thisQuestion).each(function(i) {
$(thisInput).attr('data-slider-index', (i+1));
var thisSGQA = $(this).attr('id').replace(/javatbd/, '');
var thisCode = $(this).attr('id').split('X'+qID)[1];
// Slider initial settings
var thisHTML = $('label', this).html();
setTimeout(function() {
$('div.items-start').append('<div class="card draggable" data-sgqa="'+thisSGQA+'" data-code="'+thisCode+'">\
var thisSliderMin = $(thisInput).bootstrapSlider('getAttribute', 'min');
'+thisHTML+'\
$(thisInput).bootstrapSlider('setAttribute', 'min', thisSliderMin+(i));
</div>');
var thisSliderMax = $(thisInput).bootstrapSlider('getAttribute', 'max');
});
$(thisInput).bootstrapSlider('setAttribute', 'max', thisSliderMax-((itemsLength-1)-i));
// Make the "cards" draggable
if(thisVal == '') {
$('.draggable').draggable({  
$(thisInput).val('');
revert: "invalid",
}
zIndex: 2700,
}, 200);
helper: 'original',
start: function( event, ui ) {
});
$(ui.helper).addClass('ui-draggable-helper');
},
// Slider order
stop: function( event, ui ) {
if(opts.randomOrder == true) {
}
shuffleArray(opts.order);
}
$(opts.order).each(function(i, val) {
$('.subquestion-list.questions-list', thisQuestion).append($('.question-item[data-item="'+val+'"]', thisQuestion));
});
// Listeners on the sliders
$('input[type=text]', thisQuestion).on('slide', function(event) {
handleVwpSliders(this, $(this).bootstrapSlider('getValue'));
});
$('input[type=text]', thisQuestion).on('slideStop', function(event) {
handleVwpSliders(this, $(this).bootstrapSlider('getValue'));
});
// A function to handle the siders
function handleVwpSliders(el, sliderVal) {
var movedIndexNum = $(el).attr('data-slider-index');
var lowerSliders = $('input[type=text]', thisQuestion).filter(function() {
return $(this).attr('data-slider-index') < movedIndexNum;
});
var higherSliders = $('input[type=text]', thisQuestion).filter(function() {
return $(this).attr('data-slider-index') > movedIndexNum;
});
$(lowerSliders).each(function(i) {
var thisIndexNum = $(this).attr('data-slider-index');
var newSliderVal = sliderVal-(movedIndexNum-thisIndexNum);
if($(this).bootstrapSlider('getValue') > newSliderVal) {
var thisRow = $(this).closest('.question-item');
var thisTooltip = $('.tooltip', thisRow);
$(this).bootstrapSlider('setValue', newSliderVal);
$('.tooltip-inner', thisTooltip).text($('input.text', thisRow).val());
thisTooltip.show().css('margin-left', '-'+(thisTooltip.width()/2)+'px');
var validationInput = $('input.em_sq_validation', thisRow);
$(validationInput).val($('input.text', thisRow).val())
fixnum_checkconditions($(validationInput).attr('value'), $(validationInput).attr('name'), $(validationInput).attr('type'));
}
});
$(higherSliders).each(function(i) {
var thisIndexNum = $(this).attr('data-slider-index');
var newSliderVal = sliderVal+(thisIndexNum-movedIndexNum);
if($(this).bootstrapSlider('getValue') < newSliderVal) {
var thisRow = $(this).closest('.question-item');
var thisTooltip = $('.tooltip', thisRow);
$(this).bootstrapSlider('setValue', newSliderVal);
$('.tooltip-inner', thisTooltip).text($('input.text', thisRow).val());
thisTooltip.show().css('margin-left', '-'+(thisTooltip.width()/2)+'px');
var validationInput = $('input.em_sq_validation', thisRow);
$(validationInput).val($('input.text', thisRow).val())
fixnum_checkconditions($(validationInput).attr('value'), $(validationInput).attr('name'), $(validationInput).attr('type'));
}
});
}
// Listener on resizing (override the bootstrap callout behaviour)
$(window).resize(function() {
setTimeout(function() {
$('input[type=text]', thisQuestion).each(function(i) {
if($(this).val() != '') {
var thisRow = $(this).closest('.question-item');
var thisTooltip = $('.tooltip', thisRow);
$('.tooltip-inner', thisTooltip).text($.trim($(this).val()));
console.log($('.tooltip-inner', thisTooltip).text());
thisTooltip.show().css('margin-left', '-'+(thisTooltip.width()/2)+'px');
}
});
}, 1);
});
});
};
})( jQuery );</syntaxhighlight>
#Add the following to the end of template.css (this is for a copy of the default template):<syntaxhighlight lang="css">/*********** Van Westendorp Pricing ***********/
 
.vwp-question .control-label {
    margin-bottom: 35px;
}
 
.vwp-question .withslider {
    margin-bottom: 0px;
}
 
.vwp-question .slider-container {
    margin-bottom: 2.5em;
    margin-top: 3.5em;
}
 
.vwp-question .slider.slider-horizontal .slider-handle {
    margin-top: -5px;
}
 
@media (max-width: 600px){
 
.vwp-question .withslider {   
padding-left: 0;
padding-right: 0;
}
}
 
@media (max-width: 480px){
 
#outerframeContainer,
.vwp-question .control-label,
.vwp-question .slider-container > div {   
padding-left: 0;
padding-right: 0;
}
.vwp-question .slider-container > div > div {   
padding-left: 5px;
padding-right: 5px;
}
}</syntaxhighlight>
#To apply the plugin, add this script the multiple-numeric question source.
There are two options to adjust<br /> -'''order''' - this is the order that your subquestions will be displayed (default, 1, 2, 3, 4)<br /> -'''randomOrder''' - if set to true, the '''order''' option will be overridden and the subquestion order will be randomized (default, false)<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">
$(document).ready(function(){
$('#question{QID}').vWPricing25({
order: [1, 2, 3, 4],
randomOrder: false
});
});
});
</script></syntaxhighlight>
// Set the targets for the draggables
== Version 3.x ==
$('.droppable.items-start').droppable({
 
hoverClass: 'target-hover',
A custom question theme for Pricing Sliders can be found here - https://github.com/tpartner/LimeSurvey-Pricing-Sliders-3.x
accept: '.draggable.moved'
 
});
= Multiple Choice - Select "All of the above" =
$('.droppable.items-end').droppable({
== Version 2.67.x ==
hoverClass: 'target-hover',
 
accept: '.draggable'
''Tested with: LimeSurvey version 2.67.3''
});
 
This script will render the last item of a multiple-choice question as "Select all".<br />
// After dropped
 
$('.droppable').bind('drop', function(event, ui) {
'''Download''' a [[Media:All_of_the_above.zip|sample survey]].
 
// Physically move the draggable to the target
'''Implementation''':
// (the plugin just visually moves it)
 
// (need to use a clone here to fake out iPad)
#[[Workarounds: Manipulating a survey at runtime using Javascript#How to use Script (eg. JavaScript etc.) in LimeSurvey?|Set up your survey to use JavaScript]].
var newDraggable = $(ui.draggable).clone();
#Create your Multiple Choice question with the last subquestions as "Select all".
$(newDraggable).appendTo(this);
#Add the following script to the source of the question text:<syntaxhighlight lang="javascript"><script type="text/javascript" charset="utf-8">
$(ui.draggable).remove();
$(document).on('ready pjax:complete',function() {
if($(this).hasClass('items-end')) {
$(newDraggable).addClass('moved');
// Identify this question
var thisQuestion = $('#question{QID}');
// Initial states
if($('input[type="checkbox"]:last', thisQuestion).is(':checked')) {
$('input[type="checkbox"]', thisQuestion).not(':last').prop('checked', true).prop('disabled', true);
}
}
else {
$(newDraggable).removeClass('moved');
// Listener on the last checkbox
}
$('input[type="checkbox"]:last', thisQuestion).on('change', function(e) {
$(newDraggable).removeClass('ui-draggable-helper ui-draggable-dragging').css({
if($(this).is(':checked')) {
'z-index':'',
$('input[type="checkbox"]', thisQuestion).not(this).prop('checked', true).prop('disabled', true);
'top':'auto',  
}
'left':'auto'
else {
$('input[type="checkbox"]', thisQuestion).not(this).prop('checked', false).prop('disabled', false);
}
// Fire ExpressionScript
$('input[type="checkbox"]', thisQuestion).not(this).each(function(i) {
checkconditions(this.value, this.name, this.type);
});
});
});
   
   
// Now, make this new clone draggable
// Re-enable checkboxes so data gets stored
$(newDraggable).draggable({
$('#moveprevbtn, #movenextbtn, #movesubmitbtn').on('click', function(){
revert: "invalid",
$('input[type="checkbox"]', thisQuestion).prop('disabled', false);
zIndex: 2700,
helper: 'original',
start: function( event, ui ) {
$(ui.helper).addClass('ui-draggable-helper');
},
stop: function( event, ui ) {
}
});
});
});
});
</script></syntaxhighlight>
// Initial "card" positions
 
$('li.question-item input.text', thisQuestion).each(function(i) {
= Pay Off Matrix Interface =
if($(this).val() > 0) {
== LimeSurvey Version 2.67.x==
$('.items-end[data-items-end="'+$(this).val()+'"]').append($('.card[data-sgqa="'+$(this).attr('name')+'"]'));
 
$('.card[data-sgqa="'+$(this).attr('name')+'"]').appendTo($('.items-end[data-items-end="'+$(this).val()+'"]')).addClass('moved');
This workaround converts a list-radio type question into a Pay Off Matrix interface.<br /><br />
}
 
[[File:Pay_off_matrix_1.png]]<br />
[[File:Pay_off_matrix_2.png]]<br /><br />
 
'''Download''' a [[Media:Demo_Pay_Off_Matrix.zip|working template and survey]] (un-zip the package and install the template before the survey).
 
'''Implementation''':
 
#Create your List Radio question with with the upper and lower values for each answer separated by "pipe" characters.<br />[[File:Pay_off_matrix_3.png]]
#Assign a class "pay-off-matrix" to that question.<br />[[File:Pay_off_matrix_4.png]]
#Add the following to the end of template.js:<syntaxhighlight lang="javascript">$(document).on('ready pjax:complete',function() {
// Apply the payOffMatrix plugin
$('.pay-off-matrix').payOffMatrix({
topText: 'You receive',
bottomText: 'Others receive'  
});
});
});
/*****
    A jQuery plugin to convert a list-radio question to a Pay Off Matrix
    Copyright (C) 2017 - Tony Partner (http://partnersurveys.com)
    Licensed MIT, GPL
    Version - 1.0
    Create date - 25/09/2017
*****/
(function($){
$.fn.payOffMatrix = function(options) { 
   
   
// Interrupt the Next/Submit function and load the inputs
var opts = $.extend( {
$('form#limesurvey').submit(function(){
topText: '',
$('.question-item input.text', thisQuestion).val(0);
bottomText: ''
$('.droppable.items-end .card', thisQuestion).each(function(i) {
}, options);
var thisItemsEnd = $(this).closest('.droppable.items-end').attr('data-items-end');
var thisID = $(this).attr('data-sgqa');
return this.each(function() {
$('#answer'+thisID+'').val(thisItemsEnd);
var thisQuestion = $(this);
 
// Insert the matrix elements
$('div.answer', thisQuestion).prepend('<div class="pom-outer-wrapper"><div class="pom-wrapper">\
<div class="pom-row-label top">'+opts.topText+'</div>\
<div class="pom-items-wrapper">\
</div>\
<div class="pom-row-label bottom">'+opts.bottomText+'</div>\
<div style="clear:both;" />\
</div></div>');
$('.answer-item', thisQuestion).each(function(i) {
var thisValue = $('input.radio', this).attr('value');
var itemTop = $.trim($('.label-text', this).text()).split('|')[0];
var itemBottom = $.trim($('.label-text', this).text()).split('|')[1];
$('.pom-items-wrapper', thisQuestion).append('<div class="pom-item">\
<div class="pom-item-label-wrapper top">\
<div class="pom-item-label">'+itemTop+'</div>\
</div>\
<div class="pom-item-button-wrapper">\
<div class="pom-item-button" data-value="'+thisValue+'"></div>\
</div>\
<div class="pom-item-label-wrapper bottom">\
<div class="pom-item-label">'+itemBottom+'</div>\
</div>\
<div class="under-480" style="clear:both;" />\
</div>');
});
$('.pom-item:first', thisQuestion).addClass('first');
$('.pom-item:last', thisQuestion).addClass('last');
// Listeners on the matrix events
$('.pom-item-button, .pom-item-label', thisQuestion).on('click', function(e) {
$('.pom-item', thisQuestion).removeClass('active-item');
$(this).closest('.pom-item').addClass('active-item');
var thisValue = $(this).closest('.pom-item').find('.pom-item-button').attr('data-value');
$('input.radio[value="'+thisValue+'"]', thisQuestion).trigger('click');
}).hover(
function() {
$(this).closest('.pom-item').addClass('hover');
}, function() {
$( this ).closest('.pom-item').removeClass('hover');
}
);
});
});
});
}</syntaxhighlight>
};
})(jQuery);</syntaxhighlight>
#Add the following to the end of template.css:<syntaxhighlight lang="css">/*****
    Styles for the payOffMatrix plugin
    Copyright (C) 2017 - Tony Partner (http://partnersurveys.com)
    Licensed MIT, GPL
    Version - 1.0
    Create date - 25/09/2017
*****/
 
.pay-off-matrix .answers-list {
position: absolute;
left: -9999em;
}


<br />5) Add the following to the end of template.css:<syntaxhighlight lang="css" enclose="div">
.pom-wrapper {
/* Card Sort */
position: relative;
margin: 0 auto;
.card-sort-question ul.subquestions-list {
width: 100%;
/*display: none;
max-width: 648px;
clear: both;*/
}
}
 
.card-sort-question .items-start {
.pom-item {
float: left;
float: left;
width: 340px;
height: 317px;
margin-top: 8px;
border: 1px solid #666;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
-khtml-border-radius: 6px;
border-radius: 6px;
}
}
 
.card-sort-question .items-start.target-hover {
.pom-row-label {
background:#C9C9C9;
clear: both;
text-align: center;
font-size: 16px;
}
 
.pom-row-label.top {
padding-top: 5px;
}
}
 
.card-sort-question .items-end-wrapper {
.pom-row-label.bottom {
float: left;
padding-bottom: 5px;
margin-left: 20px;
width: 340px;
}
}
 
.card-sort-question .items-end {
.pom-item-label-wrapper {
width: 338px;
padding: 5px 8px;
min-height: 73px;
margin-bottom: 10px;
padding-bottom: 5px;
border: 1px solid #666;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
-khtml-border-radius: 6px;
border-radius: 6px;
background: #EBEBEB;
}
}
 
.card-sort-question .items-end.target-hover {
.pom-item-label-wrapper.top {
background: #C9C9C9;
margin-bottom: 3px;
}
}
 
.card-sort-question .items-end.ui-state-disabled {
.pom-item-label-wrapper.bottom {
    opacity: 1;
margin-top: 3px;
filter:alpha(opacity=100);
}
}
 
.card-sort-question .items-end-text {
.pom-item {
width: 338px;
width: 11.11111111111111%;
padding-bottom: 5px;
}
background: #FFFFFF;
 
font-size: 110%;
.pom-item-label {
border: 1px solid #233140;
background: #EBEBEB;
-moz-transition: background-color 300ms ease;
-o-transition: background-color 300ms ease;
-webkit-transition: background-color 300ms ease;
transition: background-color 300ms ease;
text-align:center;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
-khtml-border-radius: 5px;
border-radius: 5px;
font-size: 16px;
line-height: 30px;
}
 
.pom-item.hover .pom-item-label,
.pom-item.active-item .pom-item-label {
font-weight: bold;
font-weight: bold;
cursor: pointer;
}
}
 
.card-sort-question .card  {
.pom-item.active-item .pom-item-label {
display: inline-block;
background: #233140;
width: 140px;
color: #FFFFFF;
height: auto;
margin: 5px 8px;
padding: 3px;
    border: 2px solid #2F4354;
-moz-border-radius: 8px;
-webkit-border-radius: 8px;
-khtml-border-radius: 8px;
border-radius: 8px;
background-color: #43b3d1;
color: #2f4354;
font-weight: bold;
text-align: center;
line-height: normal;
}
}
 
.card-sort-question .items-end .card  {
.pom-item-button-wrapper {
margin: 5px 7px;
padding: 5px 15.27777777777778%;
    background: #CCD5DD;
width: 69.44444444444444%;
width: auto;  
}
}
.card-sort-question  div.answer {
clear: both;
padding-top: 1px;
margin-top: 0;
}</syntaxhighlight>


<br />6) Add this to the source of the question to apply the function.<syntaxhighlight lang="javascript" enclose="div">
.pom-item.first .pom-item-button-wrapper {
<script type="text/javascript" charset="utf-8">
-moz-border-radius: 5px 0 0 5px;
$(document).ready(function() {
-webkit-border-radius: 5px 0 0 5px;
cardSort({QID});
-khtml-border-radius: 5px 0 0 5px;
    });
border-radius: 5px 0 0 5px;
</script></syntaxhighlight>
}
 
.pom-item.last .pom-item-button-wrapper {
-moz-border-radius: 0 5px 5px 0;
-webkit-border-radius: 0 5px 5px 0;
-khtml-border-radius: 0 5px 5px 0;
border-radius: 0 5px 5px 0;
}
 
.pom-item-button {
    height: 0;
    width: 100%;
padding-top: 100%;
    background: #FFFFFF;
-moz-transition: background-color 300ms ease;
-o-transition: background-color 300ms ease;
-webkit-transition: background-color 300ms ease;
transition: background-color 300ms ease;
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
-khtml-border-radius: 50%;
border-radius: 50%;
    cursor: pointer;
}
 
.pom-item.hover .pom-item-button {
    background-color: #5b7fa5;
}
 
.pom-item.active-item .pom-item-button,
.pom-item.active-item .pom-item-button:hover {
background-color: #233140;
cursor:default;
}
 
@media only screen and (max-width: 480px) {
.pom-outer-wrapper {
margin: 0 auto;
padding-top: 50px;
width: 256px;
}
.pom-wrapper {
display:block;
position: relative;
}
.pom-row-label {
bottom: 100%;
left: 0;
margin-left: -25px;
position: absolute;
text-align: center;
width: 150px;
background-color: transparent !important;
}
.pom-row-label.bottom {
left: auto;
right: 0;
margin-right: -25px;
padding: 5px 0 0 0;
}
pom-items-wrapper {
vertical-align: middle;
}
.pom-item {
float: none;
width: auto;
}
.pom-item-label-wrapper {
float: left;
margin: 0 !important;
padding: 7px 24px;
}
.pom-item-label {
width: 50px;
}
.pom-item-button-wrapper {
float: left;
margin: 0 5px;
padding: 5px;
width: 46px;
}
.pom-item.first .pom-item-button-wrapper {
-moz-border-radius: 5px 5px 0 0;
-webkit-border-radius: 5px 5px 0 0;
-khtml-border-radius: 5px 5px 0 0;
border-radius: 5px 5px 0 0;
}
.pom-item.last .pom-item-button-wrapper {
-moz-border-radius: 0 0 5px 5px;
-webkit-border-radius: 0 0 5px 5px;
-khtml-border-radius: 0 0 5px 5px;
border-radius: 0 0 5px 5px;
}
.pom-item .under-480 {
display: block !important;
}
}</syntaxhighlight>
 
== LimeSurvey Version 3.x==
Here is a custom question theme for LS 3.x - https://github.com/tpartner/LimeSurvey-Social-Value-Orientation
= Number Input Spinner =
<p>Tested with: ''LimeSurvey 3.28''</p>
<p>I created this control so that users can indicate their number of days and half days of work. They should not be able to indicate anything other than numbers rounded to 0.5. The original source of this code can be found here: https://codepen.io/famed/pen/gwQPyQ.</p>
[[File:inputSpinner.png]]
==How to use it==
===Save the JavaScript and the CSS code===
<syntaxhighlight lang="php">
<script>
'use strict';
 
function ctrl(numInputId, spinnerId, increment_min = 1, increment_max = 10, maxNumInput = 999999999) {
let _this = this;
this.setNumInput = function(numVal) {
let eventChange = new Event('change', { 'bubbles': true, 'cancelable': true });
document.getElementById(numInputId).value = numVal;
document.getElementById(numInputId).dispatchEvent(eventChange);
};
this.counter = 0;
this.els = {
decrement_min: document.getElementById(spinnerId).querySelector('.ctrl__button_min_decrement'),
decrement_max: document.getElementById(spinnerId).querySelector('.ctrl__button_max_decrement'),
counter: {
container: document.getElementById(spinnerId).querySelector('.ctrl__counter'),
num: document.getElementById(spinnerId).querySelector('.ctrl__counter-num'),
input: document.getElementById(spinnerId).querySelector('.ctrl__counter-input')
},
increment_min: document.getElementById(spinnerId).querySelector('.ctrl__button_min_increment'),
increment_max: document.getElementById(spinnerId).querySelector('.ctrl__button_max_increment')
};
this.decrement_min = function() {
let counter = parseFloat(_this.getCounter());
let nextCounter = (counter-increment_min >= 0) ? counter-increment_min : 0;
_this.setCounter(nextCounter);
_this.setNumInput(nextCounter);
};
this.increment_min = function() {
let counter = parseFloat(_this.getCounter());
let nextCounter = ((counter + increment_min) <= maxNumInput) ? counter + increment_min : maxNumInput;
_this.setCounter(nextCounter);
_this.setNumInput(nextCounter);
};
this.decrement_max = function() {
let counter = parseFloat(_this.getCounter());
let nextCounter = (counter-increment_max >= 0) ? counter-increment_max : 0;
_this.setCounter(nextCounter);
_this.setNumInput(nextCounter);
};
this.increment_max = function() {
let counter = parseFloat(_this.getCounter());
let nextCounter = ((counter + increment_max) <= maxNumInput) ? counter + increment_max : maxNumInput;
_this.setCounter(nextCounter);
_this.setNumInput(nextCounter);
};
this.getCounter = function() {
return _this.counter;
};
this.setCounter = function(nextCounter) {
_this.counter = nextCounter;
};
this.debounce = function(callback) {
setTimeout(callback, 50);
};
this.render = function(hideClassName, visibleClassName) {
_this.els.counter.num.classList.add(hideClassName);
setTimeout(function() {
_this.els.counter.num.innerText = _this.getCounter();
_this.els.counter.input.value = _this.getCounter();
_this.els.counter.num.classList.add(visibleClassName);
}, 100);
setTimeout(function() {
_this.els.counter.num.classList.remove(hideClassName);
_this.els.counter.num.classList.remove(visibleClassName);
}, 1100);
};
this.ready = function() {
let initNum = 0;
if (document.getElementById(numInputId).value !== "") {
initNum = document.getElementById(numInputId).value;
}
_this.counter = initNum;
_this.els.counter.num.innerText = initNum;
_this.els.counter.input.value = initNum;
_this.els.decrement_min.addEventListener('click', function() {
_this.debounce(function() {
_this.decrement_min();
_this.render('is-decrement-hide', 'is-decrement-visible');
});
});
_this.els.increment_min.addEventListener('click', function() {
_this.debounce(function() {
_this.increment_min();
_this.render('is-increment-hide', 'is-increment-visible');
});
});
_this.els.decrement_max.addEventListener('click', function() {
_this.debounce(function() {
_this.decrement_max();
_this.render('is-decrement-hide', 'is-decrement-visible');
});
});
_this.els.increment_max.addEventListener('click', function() {
_this.debounce(function() {
_this.increment_max();
_this.render('is-increment-hide', 'is-increment-visible');
});
});
_this.els.counter.input.addEventListener('input', function(e) {
let parseValue = parseFloat(e.target.value.replace(",","."));
let pattern = /^[0-9]+[,.]?[0-9]*$/;
if (pattern.test(e.target.value) && (parseValue % increment_min == 0) && (parseValue <= maxNumInput)) {
_this.setCounter(parseValue);
_this.setNumInput(parseValue);
_this.render();
}
else {
_this.setCounter(_this.getCounter());
_this.render();
}
});
_this.els.counter.input.addEventListener('focus', function(e) {
_this.els.counter.container.classList.add('is-input');
});
_this.els.counter.input.addEventListener('blur', function(e) {
_this.els.counter.container.classList.remove('is-input');
_this.render();
});
};
};
</script>  
 
 
<style type="text/css">
 
.ctrl {
flex: 0 0 auto;
display: flex;
align-items: center;
font-size: 18px;
margin-top: 10px;
}
.ctrl__counter {
position: relative;
width: 100px;
height: 40px;
color: #333C48;
text-align: center;
overflow: hidden;
background-color: #fff;
border-bottom: 1px solid #ccc;
border-top: 1px solid #ccc;
}
.ctrl__counter.is-input .ctrl__counter-num {
visability: hidden;
opacity: 0;
transition: opacity 100ms ease-in;
}
.ctrl__counter.is-input .ctrl__counter-input {
visability: visible;
opacity: 1;
transition: opacity 100ms ease-in;
}
.ctrl__counter-input {
width: 100%;
margin: 0;
padding: 0;
position: relative;
z-index: 2;
box-shadow: none;
outline: none;
border: none;
color: #333C48;
font-size: 18px;
line-height: 40px;
text-align: center;
visability: hidden;
opacity: 0;
transition: opacity 100ms ease-in;
}
.ctrl__counter-num {
position: absolute;
z-index: 1;
top: 0;
left: 0;
right: 0;
bottom: 0;
line-height: 40px;
visability: visible;
opacity: 1;
transition: opacity 1000ms ease-in;
}
.ctrl__counter-num.is-increment-hide {
opacity: 0;
transform: translateY(-50px);
-webkit-animation: increment-prev 100ms ease-in;
animation: increment-prev 100ms ease-in;
}
.ctrl__counter-num.is-increment-visible {
opacity: 1;
transform: translateY(0);
-webkit-animation: increment-next 100ms ease-out;
animation: increment-next 100ms ease-out;
}
.ctrl__counter-num.is-decrement-hide {
opacity: 0;
transform: translateY(50px);
-webkit-animation: decrement-prev 100ms ease-in;
animation: decrement-prev 100ms ease-in;
}
.ctrl__counter-num.is-decrement-visible {
opacity: 1;
transform: translateY(0);
-webkit-animation: decrement-next 100ms ease-out;
animation: decrement-next 100ms ease-out;
}
.ctrl__button {
width: 40px;
line-height: 40px;
text-align: center;
color: #fff;
cursor: pointer;
background-color: #9aa2a3;
transition: background-color 100ms ease-in;
}
.ctrl__button:hover {
background-color: #90a2b0;
transition: background-color 100ms ease-in;
}
.ctrl__button:active {
background-color: #778996;
transition: background-color 100ms ease-in;
}
.ctrl__button_max_decrement {
border-radius: 5px 0 0 5px;
border-right: 1px solid #ccc;
}
.ctrl__button_max_increment {
border-radius: 0 5px 5px 0;
border-left: 1px solid #ccc;
}
 
@-webkit-keyframes decrement-prev {
from {
opacity: 1;
transform: translateY(0);
}
}
 
@keyframes decrement-prev {
from {
opacity: 1;
transform: translateY(0);
}
}
@-webkit-keyframes decrement-next {
from {
opacity: 0;
transform: translateY(-50px);
}
}
@keyframes decrement-next {
from {
opacity: 0;
transform: translateY(-50px);
}
}
@-webkit-keyframes increment-prev {
from {
opacity: 1;
transform: translateY(0);
}
}
@keyframes increment-prev {
from {
opacity: 1;
transform: translateY(0);
}
}
@-webkit-keyframes increment-next {
from {
opacity: 0;
transform: translateY(50px);
}
}
@keyframes increment-next {
from {
opacity: 0;
transform: translateY(50px);
}
}
</style>
</syntaxhighlight>
You can save this code at three different places.
* In the LimeSurvey template: If you want to use it different surveys.
* In a group of questions: if it is a group that will include several spinner type questions.
* In the question itself, if you need only one spinner.
===Create the number input spinner with decrement and increment buttons===
<p>First yout need to create à "numerical input" question type</p>
<p>Then, in the question field editor, in "source" mode, paste this code</p>
<syntaxhighlight lang="php">
My question ...
<script>
(function() {
let controls = new ctrl("answer{SGQ}", "spinner{SGQ}", 0.5, 5, 100);
document.addEventListener('DOMContentLoaded', controls.ready);
})();
 
$(document).ready(
function(){
let answer = document.getElementById("answer{SGQ}");
if (answer.value == "") {
answer.value = 0;
}
let eventChange = new Event('change', { 'bubbles': true, 'cancelable': true });
answer.dispatchEvent(eventChange);
answer.style.display='none';
}
);  
</script>  
</syntaxhighlight>
<p>You can define increments and max input value changing the parameters when the spinner is instantiated</p>
<syntaxhighlight lang="php">
let controls = new ctrl("answer{SGQ}", "spinner{SGQ}", 0.5, 5, 100);
</syntaxhighlight>

Latest revision as of 11:48, 26 March 2024

Often you want to manipulate certain elements of a survey at runtime. The only solution without touching the source code is to use JavaScript or jQuery which is shipped with LimeSurvey. Here are some examples to hide and style elements, validate user input or do calculations at runtime.

Other workarounds can be found at


Please keep in mind that these workarounds are not official LimeSurvey extensions - they are solutions that users have created for themselves.
Therefore LimeSurvey can't offer guarantees or support for these solutions.
If you have questions, please contact the users that, thankfully, shared their solutions with the community.


Note: ExpressionScript (EM) may cause some of these work-arounds to fail. Any custom javascript included as a .js file will be fine. However, if you in-line your JavaScript code, you must make sure that you have a space after every opening (left) curly brace, and a space before every closing (right) curly brace. Otherwise, ExpressionScript (EM) will think that the content between the curly braces is something it should parse and understand. I have fixed all of the work-arounds below that can be fixed (many did not follow those rules). However, some will continue to break. If a work-around contains a regular expression which contains curly braces (to indicate a range of repeats of a sub-expression), EM will try to process those curly braces, and thus break your regular expression. Strategies to continue to support that work-around functionality are discussed below.

How to add a javascript workaround solution to this Wiki page?

Well, this is pretty easy. Click the edit icon and create your own headline within the javascript section starting with "!". Then add a short note about the version you have used when creating your workaround, you can copy/paste this code snippet ''Tested with: (enter LimeSurvey version and maybe browser)''.

Finally it's imported to mark all your code with code tags, other wise you might break this page.

  • Start tag: <syntaxhighlight lang="php">
  • End tag: </syntaxhighlight>.

How to use Script (eg. JavaScript etc.) in LimeSurvey

Plugin addScriptToQuestion allow to use a simple textarea for easily adding javascript.

To use JavaScript within LimeSurvey, the XSS filter must be turned off and the code inserted in the source of a question or a group description.

  • Go to Global settings --> Security and set "Filter HTML for XSS" to "Off".
  • Add a new question
  • Edit the question and click the "Source" button in the editor toolbar:
  
  • Enter your script after the question text:
  
  • Save the question

A simple test to see if JavaScript is enabled is to echo an alert. Use this code:

<script type="text/javascript" charset="utf-8">
   alert("Test!");
</script>

It is a good practice to use the jQuery $(document).on('ready pjax:complete') event to prevent the code from executing until the page is fully loaded, it is especially needed when using the ajax-mode in the 3.0.0 and upper default templates:

<script type="text/javascript" charset="utf-8">
   $(document).on('ready pjax:complete',function() {
       alert("Test!");
   });
</script>

Usage of brackets ({ and })

  Caution when using brackets ({ and }) in scripts : Expression manager uses brackets ({ and }) to enclose expressions. If you have to use brackets in your JavaScript, you must add a space or line feed after the opening bracket ({) and before the closing bracket (})


Examples of usage of brackets

ExpressionScript will try to parse this JavaScript:

<script type="text/javascript" charset="utf-8">
   $(function() {console.log('broken javascript');});
</script>

Adding line feed prevents ExpressionScript from parsing

<script type="text/javascript" charset="utf-8">
   $(function() {
      console.log('Adding a line feed for javascript');
   });
</script>

ExpressionScript will try to parse this JavaScript:

<script type="text/javascript" charset="utf-8">
   if(myvar==1) {console.log('broken javascript');};
</script>

Adding spaces inside the brackets prevents ExpressionScript from parsing

<script type="text/javascript" charset="utf-8">
   if(myvar==1) { console.log('Adding a space for javascript'); };
</script>

Sum up input values using Javascript

Tested with:

As of version 1.92, this is easier to do using the sum() function in ExpressionScript. See this HowTo example.
Starting in Version 1.92, you also do no longer need to use SGQA (e.g. 45251X1X5) identifiers. Instead the code {SGQ} will automatically replaced by the current questions SGQA-codes.

This is from a survey that is a invitation to a longer party, when the user clicks to the next page, the calculated results will be shown.

If you wonder what the {INSERTANS:1X6X12} stuff is, look at SGQA identifier and Using information from previous answers.

<div style="text-align: left">These are your answers:<br />

<u></u><u></u><u></u><u></u><u>=

<p><script LANGUAGE="JavaScript">

partyprice = 3;

beadclothprice = 3;

breakfastprice = 7;

lunchprice = 8;

dinnerprice = 10;

foodtotal = 0;

var isreallytrue = "Yes";

var noanswer = "No answer";

document.write( "Can you come? = {INSERTANS:45251X1X1}<BR>" );

if ("{INSERTANS:45251X1X1}" == isreallytrue )

  {

   if ("{INSERTANS:45251X1X3}"== noanswer) { answer = 0; }

   else { answer = "{INSERTANS:45251X1X3}"; }

   partypeople = parseInt(answer);

   partytotal = partyprice * partypeople;

   document.write( "It's {INSERTANS:45251X1X3} persons who will come = " + partytotal + "&euro;<BR>" );

   if ("{INSERTANS:45251X1X4}"== noanswer) { answer = 0; }

   else { answer = "{INSERTANS:45251X1X4}";}

   beadcloths = parseInt(answer);

   beadclothtotal = beadclothprice * beadcloths;

   document.write( "You want to borrow {INSERTANS:45251X1X4} beadcloths = " + beadclothtotal + "&euro;<BR>" );

   document.write( "Will you be there thursday? = {INSERTANS:45251X1X5}<BR>" );

   document.write( "On friday you will have these meals: <BR>" );

   if ("{INSERTANS:45251X1X71}"== noanswer) { answer = 0; }

   else { answer = "{INSERTANS:45251X1X71}";}

   breakfastfriday = parseInt(answer) * breakfastprice;

   document.write( "'''_ " + answer + " breakfeasts = " + breakfastfriday + "&euro;<BR>" );

   if ("{INSERTANS:45251X1X72}"== noanswer) { answer = 0; }

   else { answer = "{INSERTANS:45251X1X72}";}

   lunchfriday = parseInt(answer) * lunchprice ;

   document.write( "'''_ " + answer + " lunches = " + lunchfriday + "&euro;<BR>" );

   if ("{INSERTANS:45251X1X73}"== noanswer) { answer = 0; }

   else { answer = "{INSERTANS:45251X1X73}";}

   dinnerfriday = parseInt(answer)* dinnerprice ;

   document.write( "'''_ " + answer + " dinners = " + dinnerfriday + "&euro;<BR>" );

   foodtotal = breakfastfriday + lunchfriday + dinnerfriday;

   document.write( "Will you be there saturday? = {INSERTANS:45251X1X8}<BR>" );

   if ("{INSERTANS:45251X1X8}" == isreallytrue )

      {

       document.write( "On saturday you will have these meals: <BR>" );

       if ("{INSERTANS:45251X1X91}"== noanswer) { answer = 0; }

       else { answer = "{INSERTANS:45251X1X91}"; }

       breakfastsaturday = parseInt(answer) * breakfastprice ;

       document.write("'''_ " + answer + " breakfeasts = " + breakfastsaturday + "&euro;<BR>" );

       if ("{INSERTANS:45251X1X92}"== noanswer) { answer = 0; }

       else { answer = "{INSERTANS:45251X1X92}"; }

       lunchsaturday = parseInt(answer) * lunchprice ;

       document.write( "'''_ " + answer + " lunches = " + lunchsaturday + "&euro;<BR>" );

       if ("{INSERTANS:45251X1X93}"== noanswer) { answer = 0; }

       else { answer = "{INSERTANS:45251X1X93}"; }

       dinnersaturday = parseInt(answer) * dinnerprice ;

       document.write( "'''_ " + answer + " dinners = " + dinnersaturday + "&euro;<BR>" );

       foodtotal = foodtotal + breakfastsaturday + lunchsaturday + dinnersaturday;

      }

   document.write( "Will you be there sunday? = {INSERTANS:45251X1X10}<BR>" );

   if ("{INSERTANS:45251X1X10}" == isreallytrue )

      {

       document.write( "On sunday you will have these meals: <br>" );

       if ("{INSERTANS:45251X1X111}"== noanswer) { answer = 0; }

       else { answer = "{INSERTANS:45251X1X111}"; }

       breakfastsunday = parseInt(answer) * breakfastprice ;

       document.write( "'''_ " + answer + " breakfeasts = " + breakfastsunday + "&euro;<BR>"  );

       if ("{INSERTANS:45251X1X112}"== noanswer) { answer = 0; }

       else { answer = "{INSERTANS:45251X1X112}"; }

       lunchsunday = parseInt(answer) * lunchprice ;

       document.write( "'''_ " + answer + " lunches = " + lunchsunday + "&euro;<BR>" );

       if ("{INSERTANS:45251X1X113}"== noanswer) { answer = 0; }

       else { answer = "{INSERTANS:45251X1X113}"; }

       dinnersunday = parseInt(answer) * dinnerprice ;

       document.write( "'''_ " + answer + " dinners = " + dinnersunday + "&euro;<p>" );

       foodtotal = foodtotal + breakfastsunday + lunchsunday + dinnersunday;

      }

   total = partytotal + beadclothtotal + foodtotal;

   document.write( "<h3>In total it will cost you " + total + "&euro;</h3><BR>

   Put that sum in this bank account 1234 5678 9012" );

  }

</SCRIPT></p>

</div>

Multiple Question Validation

Tested with:

As of version 1.92, this is easier to do using the min/max/equals_num_value attributes (to validate sums), or the em_validation_q function, which lets you refer to other variables when you validate the current question ExpressionScript. Such validations also work on the current page, and properly handle array_filter and cascading relevance.

This workaround is in case you need to do some validation involving more than one question, it implies a very little JavaScript programming and the use of SGQA Identifiers; this should work on almost any version of LimeSurvey.

Is easy to explain with an example, we need to do a survey of the percentage of males and females and we want to be sure that the sum is 100, to do this follow this steps (the necessary data that is not indicated in these instructions are not important i.e. question codes, etc.):

  1. Create a new survey of type "group by group", set the "Show |<< Prev| button" to yes and note the SID.
  2. Add a new group called "Group 1" and note the GID.
  3. Add a new question "Percent of males" of type "numeric", make it mandatory and note the QID (we will refer to this as QID1).
  4. Add a new question "Percent of females" of type "numeric", make it mandatory and note the QID (we will refer to this as QID2).
  5. Add a new group called "Group 2"
  6. Add a new non mandatory question, here is the trick the question text has to be (be sure to replace the SID, GID, QID1 and QID2
<script>

function validation()

{

   if [[{INSERTANS:SIDXGIDXQID1}+{INSERTANS:SIDXGIDXQID2}) != 100)

   {

     alert("Your responses don't sum 100! Check them");

     document.limesurvey.move.value = 'moveprev';

     document.limesurvey.submit();

   }

   else

   {

     document.limesurvey.move.value = 'movelast';

     document.limesurvey.submit();

   }

 }

 setTimeout("validation()",250);

 </script>

But this will show shortly the question and will jump to the end page, a little weird for the survey taker but works.

This is a proof of concept, and may have some problems, please if you notice something or improve it send me an e-mail to leochaton-limesurvey at yahoo dot com.

This will work for 2 questions that you want to be the same. Works well for email and confirm email questions

 function validation() {

 if ("{INSERTANS:SIDXGIDXQID}" != "{INSERTANS:SIDXGIDXQID}") {

    alert("Your responses don't match Check them"); document.limesurvey.move.value = 'moveprev';

    document.limesurvey.submit();

   }

 }

 setTimeout("validation()",250);

Alternate exit

Tested with:

Using the same idea of the previous post, you can implement an alternate exit of your survey.

This script should be in the first question to be displayed in a next page after the question that should trigger the alternate exit. In this example the question that trigger the alternate exit is a Yes/No question, if the user chooses no, he is redirected to another page.

<script>

if ("{INSERTANS:SIDXGIDXQID}" == "No")

{

  window.location="http://www.limesurvey.org";

}

</script>

Custom onload function

Tested with: 'All browsers', LimeSurvey 1.70 and up

General

This simple workaround describes how to add a custom Javascript code that is triggered right after the page is loaded. It may have numerous applications, especially when you deal with a custom JS/HTML code. It may be used to hide form elements (<input> tags), make them read-only, display alert messages, etc.

To run any Javascript code right after page load use the Query ready() function. It will be executed when the page is being loaded. For example when you add the following code to one of your question definitions...:

<script>

jQuery(document).ready(

   function(){

       alert('onload alert!');

   }

);

</script>

...it will display a pop-up message immediately after the page has been loaded.

Some of the applications of this trick I've explored are:

a) Hiding the form elements by calling in your Custom_On_Load:

document.getElementById("answer73166X16X54").style.display='none';

Note: As of svn build 9755, you can now dynamically reference the questions input fields by using the {SGQ} keyword. You can rewrite the previous example like this:

document.getElementById("answer{SGQ}").style.display='none';

this makes it a little easier when exporting surveys, so you don't have to worry about manually entering each questions SurveyGroupQuestion ID.

b) Making the elements read only:

document.getElementById("answer73166X16X54").readOnly=true;

where the "answer73166X16X54" structure has been described in one of other workarounds above.

c) For Hiding by a lot of elements by calling in your Customer_On_Load

jQuery searchs in html document for input-fields with radio type, if his answer text empty then do the display css set to none.

jQuery(document).ready(

           function() {

             jQuery('input:radio').each(

               function() {

                 var s = jQuery(this);

                 var c = s.parent().children('.answertext');

                 for(var i=0; i < c.length; i++) {

                   var a = jQuery(c[i]]);

                   if(this.id </u> a.attr('for') && a.text() == '') {

                     s.css({ 'display' : 'none' });

                     a.css({ 'display' : 'none' });

                     break;

                   }

                 }

               }

             )

           }

         );

Advanced usage

If you want to place global Javascript code using the above jQuery(document).ready function then the best place to do this is the template javascript file template.js. You can edit this file using the template editor. Please be aware that placing it in the template javascript file will make the function available to all surveys using that template. If needed just create a new template copy for your survey.

Alternatively you can place the jQuery(document).ready function in the group description to make a Javascript function only available to a certain question group.

Language-specific Javascript code

There is an easy way to find out the current language and execute according Javascript using jQuery: The <html> tag of any survey pages contains an 'lang' attribute which holds the current language code. You can read that language code by using the followin code

jQuery(document).ready(

   function(){

       languagecode=$('html').attr('lang');

       alert('This is the current language code: '+languagecode);

   }

);

For an English survey the above code would show a messagebox with "This is the current language code: en"

Custom onload function in multi-language survey

Tested with: 1.85+ (7253), IE 6/7, FireFox 3.0, Safari 3.2,

To use custom onload functions, as above, in multi-language surveys you must define the functions in the question source for all languages.

While this initially seems to be a bit tedious, it actually can be useful - you can alter your onload functions depending on the language in use. For example, you can dynamically display alerts or element contents in the current language.

Filter answers of a question with the answer of another

Method 1

Tested with: 1.70 - 1.72

As of version 1.92, this is easier to do using the the array_filter attribute, or relevance equations in general.

If you want to filter the answer of a question with another answer, you can use this workaround.

First, use answer code like that: XX for the first question, and XXYYY for the second one.

Answer of the second question filter if the 2 first letters are the answer of the first question.

We use 1000X1X1 and 1000X1X2 for the example, question had to be on separate page (question by question for example).The second question had to be a dropdown list (we can do similar workaround, but droplis is the best). You can do this using JQuery. It leaves the answer in the select box, but disables it. You could also replace "disable" with "remove" to get rid of it altogether.

var previousQuestionAnswer = "{INSERTANS:1000X1X1}";

var currentQuestionID = "#answer1000X1X1"

$(document).ready(function() {

   // See if the answer for the previous question matches an answer in the current question

   // (other than 'No Answer')

   // If so, disable it.

   if (previousQuestionAnswer!='No Answer') {

       $(currentQuestionID).children().each(function(index, Element)

       {

           if ($(this).attr('text') == previousQuestionAnswer)

               { $(this).attr('disabled', true); }

           }

       );}

}); // End of JQuery ready function

Method 2

Tested with 1.92 + Firefox 9.0

If you have a country, state, and city set of questions (or similar), you could code answer options as:

Q1 Country Single choice list (ratio, dropdown)

X country#

Q2 State Single choice list (dropdown)

XY state#

Q3 City Single choice list (dropdown)

XYZZ city#

Now you must put the following Javascript code in the source of question 2 (State):

<script type="text/javascript" charset="utf-8">

   $(document).ready(function() {

       // Get the countryCode (first character of the country)
       var countryCode = '{INSERTANS:1000X1X1}'.substr(0, 1).toUpperCase();

       // Loop through all dropdown options and remove those with codes not starting with the countryCode
       $('select[id^="answer"] option').each(function(i){

           if($(this).attr('value')  && $(this).attr('value').substr(0, 1).toUpperCase() != countryCode) {
               $(this).remove();
           }
       });
   });

</script>

And the following code in the source of question 3 (City):

<script type="text/javascript" charset="utf-8">

   $(document).ready(function() {

       // Get the stateCode (two first characters of the state)

       var stateCode = '{INSERTANS:1000X1X2}'.substr(0, 2).toUpperCase();

       // Loop through all dropdown options and remove those with codes not starting with the stateCode

       $('select[id^="answer"] option').each(function(i){

           if($(this).attr('value')  && $(this).attr('value').substr(0, 2).toUpperCase() != stateCode) {

               $(this).remove();

           }

       });

   });

</script>

Each question must be in different pages of survey to work, i.e. in a question by question survey, or in diferent groups in a group by group survey.

Note that if you choose a two characters answer option code in the State question, i.e. a XYY answer option code, you must change the substr(0, 2) part of code in question 3 to substr(0, 3).

Filter answers of a multiple numeric type question

Tested with: 1.90+

As of version 1.92, this is easier to do using the the array_filter attribute, or relevance equations in general. In 1.92, array_filter works with all questions that have subquestions (e.g. also works for multiple numeric and multiple short text).

If you want to filter a question of type "multiple numeric", you have to modify the workaround.

Note that this workaround assumes that the code for each of the subquestions are a numeric sequence 1,2,3...

<script type="text/javascript" charset="utf-8">

 $(document).ready(function(){

   // Match elements with labels using htmlFor and a loop

   var labels = document.getElementsByTagName('LABEL');

   for (var i = 0; i < labels.length; i++) {

       if (labels[i].htmlFor != '') {

            var elem = document.getElementById(labels[i].htmlFor);

            if (elem)

               elem.label = labels[i];

       }

   }

   var answerFilter="{INSERTANS:96553X63X759}";

   for (i=0; i<30; i++){

     var iLabel = document.getElementById('answer96553X63X760'+(i+1]].label.innerHTML;

     if (answerFilter.search(iLabel) == -1){

       // Hide element

       $('#question760').find('li').eq(i).hide();

       // Set value of hidden elements to zero

       document.getElementById('answer96553X63X760'+(i+1)).value = 0;

     }

   }

 });</script>

Use an answer to prefill another question (default value)

Tested with: 1.72

And tested with: 1.91+

As of version 1.92, this is easier to do using the enhanced defaults feature, which lets you pre-fill most question types with default values. In 1.91, you could set defaults for list and multiple choice questions. In 1.92, you can set defaults for any text-entry question too. Moreover, the defaults can be ExpressionScript compatible equations or tailored text. Finally, with 1.92, defaults are properly managed even when set on the same page.

This workaround gives you the ablility to use an answer to fill the default value for another question. It's explained for free text question type but can be adapted for another question type.

We use SGQA identifier. For example, we use SGQA 1000X10X20 and 1000X10X21 to prefill 1000X11X30 and 1000X11X31. For the script to work, the question has to be on a different page.

<script type="text/javascript" charset="utf-8">

$(document).ready(function() {

 $('#answer1000X11X30').val('{INSERTANS:1000X10X20}');

 $('#answer1000X11X31').val('{INSERTANS:1000X10X21}');

});

</script>

If you want use two times the value of an integer input, you can also use this to prefill a question that you hide via CSS.

<script type="text/javascript" charset="utf-8">

$(document).ready(function() {

 $('#answer1000X2X2').val(parseInt('{INSERTANS:1000X1X1}')*2);

 $("#question2").hide();

});

</script>

Now you can use {INSERTANS:1000X2X2} for giving you twice the value of the user input from question 1.

Dynamic Sum

As of version 1.92, this is easier to do with ExpressionScript using the sum() function. Not only will sums be dynamic, but they wille exclude irrelevant values, so you can array_filter a multiple numeric question and get the sum of just the answers that are visible. This HowTo example demonstrates the dynamic sum feature.
Also note, as of version 1.92, the following workaround will fail, since it includes a a regular expression containing curly braces.

This script is used with a "Multiple Options" type question calculate a total price based on how which checkboxes are selected. The prices are listed as part of the answer. The answers are set up like this: "USB Mouse ($20)". The script uses the number between the "($" and the ")" as the price for that item. You can adapt the script to if you need to change the currency or need to sum something other than money.

This script works when used in a question-by-question format. It does not work when there are more than one question on a page. I think it can be adapted to work, but I haven't done the coding yet.

Put the following code into body of your question:

<script language=Javascript>

// General definitions

// Define a regular expression to match the labels containing information about an answer

// In LimeSurvey, these labels start with "answer..."

var answerRegex = /^answer/;

// Find all answer checkboxes in the document and set their

// onchange events to updatePrice

function Custom_On_Load()

{

   // Find all input elements

   var inputList=document.getElementsByTagName("input");

   // Loop through each, looking for checkboxes

   var i;

   for( i=0; i< inputList.length;i++ )

   {

       // If input item does not have an ID, skip

       if (! (inputList[i].id)) { continue; }

       // Skip to next if ID doesn't start with "answer"

       if (!answerRegex.test(inputList[i].id)) { continue; }

       // Skip to next if not a checkbox

       if (inputList[i].type.toUpperCase()!='CHECKBOX')  { continue; }

       // This is an answer checkbox!

       // Setup onchange event

       inputList[i].onclick = updatePrice;

   }

       // Load initial sum

   updatePrice();

}

function updatePrice()

{

var labels=document.getElementsByTagName("LABEL");

// Define a regular expression to match a price inside parenthesis

// For example: ($29) or ($29.54).

// Set up

priceRegex = /\(\$(\d*\.{0,1}\d+)\)/;

// Loop through all the labels, looking for labels corresponding to

// answers that have a price in them.

var i;

var total = 0;

for( i=0; i<labels.length;i++ )

{

   var myArray;    // Define array used for regex matching

   var inputID;    // Define field element

   // Get the ID of the field corresponding to the label using the

   // "htmlFor" attribute

   // (FYI, there are some labels that will have to be screened out

   // because they don't correspond to an answer)

   // Does the label start with "answer"?

   // If not: exit.

   if (!answerRegex.test(labels[i].htmlFor)) { continue; }

   // First make sure this element exists

   // If it doesn't, go to the next element

   // If it does, it will be defined in inputID

   if (! (inputID = document.getElementById(labels[i].htmlFor)) ) { continue; }

   // See if the corresponding answer is a checkbox that is checked.

   // If not, go to next label

   if (inputID.type.toUpperCase()!='CHECKBOX')  { continue; }

   if (!inputID.checked) { continue; }

   // This is label for an answer.

   // The innerHTML will give us the text of the label

   // The price information will be in the form ($XX.XX)

   // which in contained in the label text.

   // Find a match for a decimal number inside the pattern "($" and ")"

   if [[myArray = priceRegex.exec(labels[i].innerHTML]] != null)

   {

       // Keep a running tally

       total += parseFloat(myArray[1]);

   }

}

  // Update total price on form

  document.getElementById("totalPrice").value = "$" + formatCurrency(total);

}

function formatCurrency(num) {

  num = isNaN(num) || num <u> '' || num </u> null ? 0.00 : num;

  return parseFloat(num).toFixed(2);

}

</script>

Put the following code in the help section (or wherever you want the sum to appear):

Total Cost for selected accessories: <input type="readonly" value="" id="totalPrice" />

Focus on the first Input field

(Tested with: LimeSurvey 1.80 / IE6/7, Firefox 3, Safari, Opera, chrome)

As of version 1.91, there is a built-in JavaScript function called focusFirst() to focus on the first visible element. It uses jQuery, so is more robust than this example.

Again an onload Function is needed for this tasks. I have done it the DOM way with compatibility to IE6/7.

Put the following code into the startpage.pstpl of your template, right before the </head> closing tag:

<script type="text/javascript">

   function focusFirst(Event)

   {

       var i=0;

       while(document.forms[0].elements[i].type == "hidden")

       {

           i++;

       }

       document.forms[0].elements[i].focus();

       return;

   }

   if(ie)

   { window.attachEvent("onload", focusFirst); }

   else

   { document.addEventListener("load", focusFirst, true); }

</script>

Note: I am using this with version 1.80. As changes happened to the templates in the last release, you might need to add the following before the code, if your template is older than 1.80 stable and does not include this:

<script type="text/javascript">

   var ie = false;

</script>

You will need this for the var ie to be set. Without this var, the function will not work with ie.

Answer questions by keypress

(Tested with: IE7, Firefox 3)

This workaround allow to answer the question on key 0-9 (0 is for the 10 answer).

Follow issues are supported at the moment:

  • Yes-No Questions
  • List Questions (radio)
  • other radio questions

In lists the answer limit is ten.

You supply the template "endpage" of your template with the following script.

<script type="text/javascript">

jQuery(document).ready(function() {

   jQuery(document).bind('keypress', function(e){

       if (48 <= e.which && e.which <= 57) {

           jQuery('input:radio').each(function() {

               var v = jQuery(this).val();

               var c = (e.which <u> 48) ? 10 : e.which - 48;

               if(v </u> 'Y' || v <u> 'N') {

                   v = (v </u> 'Y') ? 1 : 2;

               }

               if(v == c) {

                   jQuery(this).click();

                   jQuery('#limesurvey input:submit').click();

               }

           });

       }

   })

});

</script>

Math notation in LimeSurvey

  1. download the js file from AsciiMath script
  2. copy it in the /scripts/ folder in your installation
  3. modify the startpage.psptl of the template you want to use adding the reference to the script (if you use an older version make sure that you copy the template folder first!) for example:
<script type="text/javascript" src="ASCIIMathML.js">

Use the notation where you need it!

Note: If you use internet explorer or safari you will need to download the plugin as well, but firefox does the lot for you.

Finally, if you are confident with the limesurvey structure you could add the editor which support math notation and change the reference to the editor you want to use in the configuration file. here is the link for a TinyMCE with math support.

Filter "Array by Column" Question With "Multiple-Options"

As of version 2.06 this workaround is not required. It can be handled with subquestion relevance.

Tested with: 2.05

This workaround applies a filter to an "Array by Column" question based on the checked boxes of a previous multiple-options question. There are two methods - one for filter and array questions being on the same page and a slightly different one for the filter question being on a page before the array question.

Implementation is as follows.

FOR BOTH METHODS:

  • Set up your survey to use JavaScript.
  • If using LimeSurvey version 2.05 or newer, place the following functions in your template.js file. These will be accessed by either method.
    function filterArrByCol(qMultiOpt, qArray, prevPage) {
    
    	// If filter question is on a previous page, hide Q1 and check all "visible" boxes
    	if(prevPage == 1) {
    		$('#question'+qMultiOpt+' li[id^="javatbd"]:visible input.checkbox').attr('checked', true);
    		$('#question'+qMultiOpt+'').hide();
    	}
    	   
    	// Assign classes to the answer cells
    	$('#question'+qArray+' td.answer-item').each(function(i){
    		var classArr = $(this).attr('class').split('answer_cell_00');
    		classArr = classArr[1].split(' ');
    		var ansCode = classArr[0];
    		$(this).addClass('ans-'+ansCode+' filtered');
    	});
    	   
    	// Assign classes to the answer label cells
    	$('#question'+qArray+' table.subquestions-list tbody tr:eq(0) td.answer-item').each(function(i){
    		var classArr2 = $(this).attr('class').split(' ans-');
    		var ansCode2 = classArr2[1];
    		$('#question'+qArray+' table.subquestions-list thead tr:eq(0) th:eq('+i+')').addClass('ans-'+ansCode2+'');
    	});
    	   
    	// Fire the filter function on page load
    	filterArr(qMultiOpt, qArray);
    
    	// Listener on multi-opt checkboxes to fire the filter function
    	$('#question'+qMultiOpt+' input.checkbox').click(function(){
    		filterArr(qMultiOpt, qArray);
    	});
    	   
    	// On submit, clear all hidden radios of array
    	$('#movenextbtn, #movesubmitbtn').click(function(){
    		$('#question'+qArray+' td.filtered:hidden').each(function(i){
    				$('input.radio', this).prop('checked', false);
    		});
    		return true;
    	});
    }
    
    function filterArr(qMultiOpt, qArray) {
    
    	if($('#question'+qMultiOpt+' input.checkbox:checked').length < 1) {
    		// Hide the array if no multi-opt options are checked
    		$('#question'+qArray+'').hide();
    	}
    	else {
    		$('#question'+qArray+'').show();
    	   
    		// Hide all columns of array
    		$('#question'+qArray+' table.subquestions-list tbody td.answer-item, #question'+qArray+' table.question thead th').hide();
    	   
    		// Loop through multi-opt checkboxes and, if checked, show corresponding column of array
    		$('#question'+qMultiOpt+' input.checkbox').each(function(i){
    			if($(this).prop('checked') == true) {
    				var classArr3 = $(this).attr('id').split('X'+qMultiOpt);
    				var ansCode3 = classArr3[1];
    				$('#question'+qArray+' .ans-'+ansCode3+'').show();
    			}
    		});
    	}
    }
    
  • If using LimeSurvey version 2.0 or older, place the following functions in your template.js file. These will be accessed by either method.
    $(document).ready(function() {
    
    	function filterArrByCol(qMultiOpt, qArray, prevPage) {
    	
    		// If filter question is on a previous page, hide Q1 and check all "visible" boxes
    		if(prevPage == 1) {
    			$('#question'+qMultiOpt+' li[id^="javatbd"]:visible input.checkbox').attr('checked', true);
    			$('#question'+qMultiOpt+'').hide();
    		}
    		
    		// Assign classes to the answer cells
    		$('#question'+qArray+' table.question tbody td').each(function(i){
    			var classArr = $(this).attr('class').split('answer_cell_00');
    			var ansCode = classArr[1];
    			$(this).addClass('ans-'+ansCode+' filtered');
    		});
    		
    		// Assign classes to the answer label cells
    		$('#question'+qArray+' table.question tbody tr:eq(0) td').each(function(i){
    			var classArr2 = $(this).attr('class').split(' ans-');
    			var ansCode2 = classArr2[1];
    			$('#question'+qArray+' table.question thead tr:eq(0) th:eq('+i+')').addClass('ans-'+ansCode2+'');
    		});
    		
    		// Fire the filter function on page load
    		filterArr(qMultiOpt, qArray);
    
    		// Listener on multi-opt checkboxes to fire the filter function
    		$('#question'+qMultiOpt+' input.checkbox').click(function(){
    			filterArr(qMultiOpt, qArray);
    		});
    		
    		// On submit, clear all hidden radios of array
    		$('#movenextbtn, #movesubmitbtn').click(function(){
    			$('#question'+qArray+' td.filtered:hidden').each(function(i){
    				$('input.radio', this).attr('checked', false);
    			});
    			return true;
    		});
    	}
    	
    	function filterArr(qMultiOpt, qArray) {
    	
    		if($('#question'+qMultiOpt+' input.checkbox:checked').length < 1) {
    			// Hide the array if no multi-opt options are checked
    			$('#question'+qArray+'').hide();
    		}
    		else {
    			$('#question'+qArray+'').show();
    			
    			// Hide all columns of array
    			$('#question'+qArray+' table.question tbody td, #question'+qArray+' table.question thead th').hide();
    			
    			// Loop through multi-opt checkboxes and, if checked, show corresponding column of array
    			$('#question'+qMultiOpt+' input.checkbox').each(function(i){
    				if($(this).attr('checked') == true) {
    					var classArr3 = $(this).attr('id').split('X'+qMultiOpt);
    					var ansCode3 = classArr3[1];
    					$('#question'+qArray+' .ans-'+ansCode3+'').show();
    				}
    			});
    		}
    	}
    });
    

FOR BOTH FILTER AND ARRAY QUESTIONS ON SAME PAGE:

  • Create a multiple options question and an array-by-column question on the same page (in the same group).
  • Both questions MUST have identical subquestions and subquestion codes.
  • Place the following script in the source of one of the questions.
  • Replace "MM" with the ID of the multiple options question and "AA" with the ID of the ranking question.
  • The function will hide the array question unless at least one option is selected in the multiple-options question, in which case, it will only show the corresponding array column(s).
    <script type="text/javascript" charset="utf-8">
    	$(document).ready(function() {
    		filterArrByCol(MM, AA);
    	});
    </script>
    

FOR FILTER AND ARRAY QUESTIONS ON SEPARATE PAGES:

  • Create a multiple options question on page 1.
  • Create a multiple options question and an array-by-column question on page 2.
  • All three questions MUST have identical subquestions and subquestion codes.
  • Set the multiple options on page 2 to be filtered by the multiple options on page 1.
  • Place the following script in the source of one of the questions on page 2.
  • Replace "MM" with the ID of the multiple options question and "AA" with the ID of the array question. Do not modify the "1".
  • The function will hide the multiple options question. If options were selected in the multiple options on page 1, the corresponding options will be checked in the hidden multiple options question which will then be used to control the display of the array columns.
    <script type="text/javascript" charset="utf-8">
    	$(document).ready(function() {
    		filterArrByCol(MM, AA, 1);
    	});
    </script>
    

Auto-tabbing between text input fields

Tested with: 1.90+, IE 6/7, FireFox 3.0, Safari 3.2, Chrome 2.0

This is a workaround to facilitate auto-tabbing between text input questions in a single group using a jQuery plugin by Mathachew and the Maximum characters attribute of questions.

The behaviour will be: When the maximum number of characters has been entered into an input field, the focus (cursor) will be automatically moved to the next input field. When the backspace key is used to remove all characters from a field, the focus will be moved to the previous field. It's very useful in surveys having many text input questions to avoid having to manually tab or click between fields.

Implementation is as follows:

  1. Visit http://plugins.jquery.com/autotabs, download the plugin script and save it as jquery.autotab-1.1b.js in the template folder.
  2. Link to the script by placing <script type="text/javascript" src="{TEMPLATEURL}jquery.autotab-1.1b.js"></script> within the <head> tag of startpage.pstpl.
  3. Set up your survey to use JavaScript.
  4. Create the text based questions that auto-tabbing is to be applied to. (All must be in the same group if you're using group by group presentation)
  5. Define a Maximum characters attribute for all questions that the autotab is applied to.


Maximum characters (maximum_chars)

Description

This allows you to set the maximum number of characters that can be entered for a text based question. Entering a value of, say, 20 will mean that the participant cannot enter any more than 20 characters.

Valid values

  • Any integer value above 0


  1. In the source of the first question of the group that contains the auto-tabbed questions, add the following code.
<script type="text/javascript" charset="utf-8">

   $(document).ready(function() {

       $('#answerSSSSSXGGXAA').focus(); //initially focus on the first input

       $('#answerSSSSSXGGXAA').autotab({ target: 'answerSSSSSXGGXBB' });

       $('#answerSSSSSXGGXBB').autotab({ previous: 'answerSSSSSXGGXAA', target: 'answerSSSSSXGGXCC' });

       $('#answerSSSSSXGGXCC').autotab({ previous: 'answerSSSSSXGGXBB', target: 'answerSSSSSXGGXDD' });

       $('#answerSSSSSXGGXDD').autotab({ previous: 'answerSSSSSXGGXCC' });

   });

</script>

This example has 4 fields to be auto-tabbed between - all instances of the following must be replaced to be compatible with your survey (See image below):

  • SSSSS -> Survey ID
  • GG -> Group ID
  • AA -> First question
  • BB -> Second question
  • CC -> Third question
  • DD -> Last question

The target parameter defines where the next input is and the previous parameter defines (you guessed it) the previous input.

This plugin also has limited capability of formatting the input text - see comments in the plugin script.

Custom response listeners

Tested with: 1.85+ (7253), IE 6/7, FireFox 3.0, Safari 3.2, Chrome 2.0

Listeners can be used to perform functions when a change in an input is detected. There are many uses such as the Reverse array_filter but a simple one would be to warn a respondent if they enter a value that may be too high.

We can use the jQuery change( ) event to fire a function whenever there is a change to the input. The function will look for an input value higher than 10 and, if found, will warn the respondent.

  1. Set up your survey to use JavaScript.
  2. Place the script below in the source of one of the questions on the page where:
    • 11111 is the survey ID
    • 22 is the group ID
    • 33 is the question ID
<script type="text/javascript" charset="utf-8">

	$(document).ready(function() {

		$('#answer11111X22X33').change(function() {

			if ( $('#answer11111X22X33').val() > 10 ) {
				alert ("That's an awful lot. Are you sure?");
			}
		});
	});
</script>

Text input masks

Tested with: 1.90+, IE 7, FireFox 3.0, Safari 3.2, Chrome 2.0

There may be instances where you would like to place a mask on a text input - for example only allowing respondents to enter the correct characters for a North American phone number ((###) ###-####) or a Canadian postal code (A1A 1A1) The jQuery meioMask plugin by fabiomcosta can be used to do this.

The plugin has several pre-defined masks and supports custom masks. It also allows for pasting of input, allows default values and can auto-focus the next form element when the current input is completely filled. See http://www.meiocodigo.com/projects/meiomask/ for more details on features and options.

Implementation for this demo was as follows:

  1. Visit http://www.meiocodigo.com/projects/meiomask/, download the latest plugin script and save it as jquery.meiomask.js in the template folder.
  2. Link to the script by placing <script type="text/javascript" src="{TEMPLATEURL}jquery.meiomask.js" charset="utf-8"></script> within the <head> tag of startpage.pstpl.
  3. Turn off $filterxsshtml to allow insertion of JavaScript in the questions. (See documentation here)
  4. Create the text input questions that the masks are to be applied to
  5. In the source of the help section of the first question of the group that contains the masked inputs, add the following code. (See How to use script here)


<script type="text/javascript" charset="utf-8">

$(document).ready(function() {	
						   
	// Define custom masks
	$.mask.masks = $.extend($.mask.masks,{
		customMask1:{ mask: 'a9a 9a9' }, // Canadian postal code
		// Maximum input of 9,999.99, reverse input, default value of 0.00		
		customMask2:{ mask: '99.999,9', type : 'reverse', defaultValue : '000' } 
	});
	
	// Set the 'alt' attributes of the inputs
	$('#answer111111X22XAA').attr('alt', 'phone-us'); // a pre-defined mask					   
	$('#answer111111X22XBB').attr('alt', 'customMask1'); // a custom mask					   
	$('#answer111111X22XCC').attr('alt', 'customMask2'); // a custom mask					   
	$('#answer111111X22XDD').attr('alt', 'cc'); // a pre-defined mask for credit card
	
	// Tell the plugin to apply masks to these inputs
	$('input:#answer111111X22XAA').setMask();
	$('input:#answer111111X22XBB').setMask();
	$('input:#answer111111X22XCC').setMask();
	$('input:#answer111111X22XDD').setMask();	
});
</script>


The first section defines two custom masks to be used - one for Canadian postal code with a format of A#A #A# and a second for a maximum amount of 9999.99 with reverse input (useful for cost inputs) and a default value of 0.00.

The next section sets the "alt" attributes for the text inputs. This will tell meioMask which mask we will be applying to each input. By default meioMask looks at the alt attribute to find which mask to apply however this can be changed using the mask options.

In the final section we apply the masks to the inputs.

This example has 4 inputs with masks on them - all instances of the following must be replaced to be compatible with your survey (See image below):

  • 11111 -> Survey ID
  • 22 -> Group ID
  • AA -> First question
  • BB -> Second question
  • CC -> Third question
  • DD -> Fourth question

Text input masks - second method

Tested with: 1.91RC6+, FireFox 4.0 Tested with: 2.05, Chrome 40.0.2214.115 m (64-bit)

There may be instances where you would like to place a mask on a text input - for example only allowing respondents to enter the correct characters for a North American phone number ((###) ###-####) or a US social security number (###-##-####). The jQuery meioMask plugin by fabiomcosta is no longer supported but an alternative exists. The DigitalBush Masked Input Plugin is updated and simple to implement in LimeSurvey. See http://digitalbush.com/projects/masked-input-plugin/ for more details on features and options.

Implementation as follows:

  1. Visit http://digitalbush.com/projects/masked-input-plugin/, download the latest plugin script and save it as jquery.maskedinput.js in the template folder.
  2. Link to the script by placing <script type="text/javascript" src="{TEMPLATEURL}jquery.maskedinput.js" charset="utf-8"></script> within the <head> tag of startpage.pstpl.
  3. Turn off $filterxsshtml to allow insertion of JavaScript in the questions. (See documentation here)
  4. Create the text input questions that the masks are to be applied to
  5. In the source of the help section of the first question of the group that contains the masked inputs, add the following code. (See How to use script here)


<script type="text/javascript" charset="utf-8">
	$(document).ready(function($) {	 
		$("#answer55431X1X5").mask("999-99-9999",{ placeholder:"#" });
	});
</script>


Replace the "#answer55431X1X5" with your survey ID, group ID, and question ID. You can change the mask format and change or eliminate the placeholder if you wish. If you have multiple questions in the same group, simply copy and paste the line that begins with "$" and change the code to what is needed for your application.

Select a random response to a "Multiple options" question for later use

Tested with: LimeSurvey versions 2.06 and 2.5, IE 7-11, Firefox, Chrome

This workaround allows you to randomly select one of the checked boxes in a "Multiple options" question and store it's label for later use in the survey. A possible use-case would be to have a question asking what products were purchased and then randomly selecting one of the products to ask further questions about.

The workaround puts a listener on the checkboxes that loops through all of the checked boxes and adds their labels to an array. A random item is pulled from the array and loaded into the hidden question. This hidden question can then be used in ExpressionScript equations, relevance or conditions.

DOWNLOAD:
- Demo survey for LS 2.06
- Demo survey for LS 2.5

IMPLEMENTATION (LimeSurvey version 2.06)

  1. Set up your survey to use JavaScript.
  2. Create a "Multiple options" question
  3. Immediately following that question, create a short-text question to store the label of the checked box in (we'll hide this with JavaScript)
  4. Place the script below in the source of the multiple options question.
    <script type="text/javascript" charset="utf-8">		
    	$(document).ready(function() {	
     
    		// Identify the questions
    		var thisQuestion = $('#question{QID}');
    		var qHidden = thisQuestion.nextAll('.text-short:eq(0)');
    		var hiddenInput = $('input.text', qHidden);
    		
    		// Hide qHidden
    		qHidden.hide();
    		
    		// Listener on the checkboxes
    		$('input.checkbox', thisQuestion).on('change', function(e) {
    			handleChecked();
    		});
    		
    		// Listener on the "Other" input
    		$('input.text', thisQuestion).on('keyup change', function(e) {
    			setTimeout(function() {
    				handleChecked();
    			}, 250);
    		});
    		
    		function handleChecked() {
    			// Build an array of checked answers
    			var checkedAnswers = [];
    			$('input.checkbox:checked', thisQuestion).each(function(i) {
    				if($(this).closest('.answer-item').hasClass('other-item')) {
    					checkedAnswers.push($(this).closest('.other-item').find('input.text').val());
    				}
    				else {
    					checkedAnswers.push($(this).nextAll('label:eq(0)').text());
    				}				
    			});
    			
    			// Load the hidden question with a random item from the array
    			var checkedLength = checkedAnswers.length;
    			$(hiddenInput).val(checkedAnswers[Math.floor(Math.random()*checkedLength)]);
    			
    			// Fire ExpressionScript
    			checkconditions(hiddenInput.value, hiddenInput.name, hiddenInput.type);
    		}
        });
    </script>
    


IMPLEMENTATION (LimeSurvey version 2.5)

  1. Follow the first 3 implementation steps above
  2. Place the script below in the source of the multiple options question.
    <script type="text/javascript" charset="utf-8">		
    	$(document).ready(function() {	
     
    		// Identify the questions
    		var thisQuestion = $('#question{QID}');
    		var qHidden = thisQuestion.nextAll('.text-short:eq(0)');
    		var hiddenInput = $('input.text', qHidden);
    		
    		// Hide qHidden
    		qHidden.hide();
    		
    		// Class for "Other
    		$('input.text', thisQuestion).closest('.answer-item').addClass('other-item');
    		
    		// Listener on the checkboxes
    		$('input.checkbox', thisQuestion).on('change', function(e) {
    			handleChecked();
    		});
    		
    		// Listener on the "Other" input
    		$('input.text', thisQuestion).on('keyup change', function(e) {
    			setTimeout(function() {
    				handleChecked();
    			}, 250);
    		});
    		
    		function handleChecked() {
    			// Build an array of checked answers
    			var checkedAnswers = [];
    			$('input.checkbox:checked', thisQuestion).each(function(i) {
    				if($(this).closest('.answer-item').hasClass('other-item')) {
    					checkedAnswers.push($(this).closest('.answer-item').find('input.text').val());
    				}
    				else {
    					checkedAnswers.push($.trim($(this).nextAll('.label-text:eq(0)').text()));
    				}				
    			});
    			
    			// Load the hidden question with a random item from the array
    			var checkedLength = checkedAnswers.length;
    			$(hiddenInput).val(checkedAnswers[Math.floor(Math.random()*checkedLength)]);
    			
    			// Fire ExpressionScript
    			checkconditions(hiddenInput.value, hiddenInput.name, hiddenInput.type);
    		}
        });
    </script>
    

Display secondary options in a "Multiple options" question

Tested with: LimeSurvey versions 2.06, 2.5 and 3.x, IE 7-11, Firefox, Chrome

This workaround allows you to display secondary options in a "Multiple options" question if a primary option is checked as in the images below.

Primary option not selected:


Primary option selected:


DOWNLOADS:
- Demo template for LS version 2.06
- Demo survey for LS version 2.06.
- Demo template for LS version 2.5
- Demo survey for LS version 2.5.

LimeSurvey version 2.06

  1. Set up your survey to use JavaScript.
  2. Create a "Multiple options" question including both the primary and secondary subquestions (in the order that you would like them displayed if all shown).
  3. Add the following function to the end of template.js:
    // A function to handle "secondary" checkboxes
    function secondaryCheckboxes(qID, primaryPosition, secondaryCount) {
    	// Identify the elements
    	var thisQuestion = $('#question'+qID);
    	var primaryRow = $('li.question-item:eq('+(primaryPosition-1)+')', thisQuestion);
    	var primaryInput = $('input.checkbox', primaryRow);
    	var secondaryRows = primaryRow.nextAll('li.question-item:lt('+(secondaryCount)+')');
    	var secondaryInputs = $('input.checkbox', secondaryRows);
    	
    	// Indent the secondaries
    	secondaryRows.css({ 'margin-left':'2.5em' });
    	
    	// Initial states of the secondary answers
    	if (primaryInput.prop('checked') == false ) {
    		secondaryRows.hide(); 
    	} 
    	
    	// A listener on the primary answer to show or hide secondary answers 
    	primaryInput.click(function (event) { 
    		
    		// Hide/show the secondary answers accordingly
    		if (!$(this).is(':checked')) {
    			secondaryRows.hide();				
    			secondaryInputs.prop('checked', false);
    			secondaryInputs.each(function(i) {
    				checkconditions(this.value, this.name, this.type);
    			});
    		}
    		else {
    			secondaryRows.show(); 
    		}
    	});
    }
    
  4. Call the function in the source of the question with the question ID (no editing necessary), the start position of the primary subquestion and the number of secondaries to follow.
    So, in this example, subquestion 1 is a primary followed by 2 secondaries and subquestion 6 is a primary followed by 3 secondaries.
    <script type="text/javascript" charset="utf-8">	
    	$(document).ready(function() {
    		// Sub-question 1 is primary followed by 2 secondaries
    		secondaryCheckboxes({QID}, 1, 2);
    		// Sub-question 6 is primary followed by 3 secondaries		
    		secondaryCheckboxes({QID}, 6, 3);
        });	
    </script>
    

LimeSurvey version 2.5

  1. Follow the first 2 implementation steps above
  2. Add the following function to the end of template.js:
    // A function to handle "secondary" checkboxes
    function secondaryCheckboxes(qID, primaryPosition, secondaryCount) {
    	// Identify the elements
    	var thisQuestion = $('#question'+qID);
    	$('div.question-item', thisQuestion).parent().addClass('answer-row');
    	var primaryRow = $('div.question-item:eq('+(primaryPosition-1)+')', thisQuestion).closest('.answer-row');
    	var primaryInput = $('input.checkbox', primaryRow);
    	var secondaryRows = primaryRow.nextAll('div.answer-row:lt('+(secondaryCount)+')');
    	var secondaryInputs = $('input.checkbox', secondaryRows);
    	
    	// Indent the secondaries
    	secondaryRows.css({ 'margin-left':'2.5em' });
    	
    	// Initial states of the secondary answers
    	if (primaryInput.prop('checked') == false ) {
    		secondaryRows.hide(); 
    	} 
    	
    	// A listener on the primary answer to show or hide secondary answers 
    	primaryInput.click(function (event) { 
    		
    		// Hide/show the secondary answers accordingly
    		if (!$(this).is(':checked')) {
    			secondaryRows.hide();				
    			secondaryInputs.prop('checked', false);
    			secondaryInputs.each(function(i) {
    				checkconditions(this.value, this.name, this.type);
    			});
    		}
    		else {
    			secondaryRows.show(); 
    		}
    	});
    }
    
  3. Call the function in the source of the question with the question ID (no editing necessary), the start position of the primary subquestion and the number of secondaries to follow.
    So, in this example, subquestion 1 is a primary followed by 2 secondaries and subquestion 6 is a primary followed by 3 secondaries.
    <script type="text/javascript" charset="utf-8">	
    	$(document).ready(function() {
    		// Sub-question 1 is primary followed by 2 secondaries
    		secondaryCheckboxes({QID}, 1, 2);
    		// Sub-question 6 is primary followed by 3 secondaries		
    		secondaryCheckboxes({QID}, 6, 3);
        });	
    </script>
    

LimeSurvey version 3.x

  1. Follow the first 2 implementation steps above
  2. Add the following function to the end of custom.js:
    // A function to handle "secondary" checkboxes
    function secondaryCheckboxes(qID, primaryPosition, secondaryCount) {
    	// Identify the elements
    	var thisQuestion = $('#question'+qID);
    	var primaryRow = $('li.question-item:eq('+(primaryPosition-1)+')', thisQuestion).closest('li.question-item');
    	var primaryInput = $('input:checkbox', primaryRow);
    	var secondaryRows = primaryRow.nextAll('li.question-item:lt('+(secondaryCount)+')');
    	var secondaryInputs = $('input:checkbox', secondaryRows);
    	
    	// Indent the secondaries
    	secondaryRows.css({ 'margin-left':'2.5em' });
    	
    	// Initial states of the secondary answers
    	if (primaryInput.prop('checked') == false ) {
    		secondaryRows.hide(); 
    	} 
    	
    	// A listener on the primary answer to show or hide secondary answers 
    	primaryInput.on('change', function (event) { 
    		
    		// Hide/show the secondary answers accordingly
    		if (!$(this).is(':checked')) {
    			secondaryRows.hide();				
    			secondaryInputs.prop('checked', false).trigger('change');
    		}
    		else {
    			secondaryRows.show(); 
    		}
    	});
    }
    
  3. Call the function in the source of the question with the question ID (no editing necessary), the start position of the primary subquestion and the number of secondaries to follow.
    So, in this example, subquestion 1 is a primary followed by 2 secondaries and subquestion 6 is a primary followed by 3 secondaries.
    <script type="text/javascript" charset="utf-8">	
    	$(document).ready(function() {
    		// Sub-question 1 is primary followed by 2 secondaries
    		secondaryCheckboxes({QID}, 1, 2);
    		// Sub-question 6 is primary followed by 3 secondaries		
    		secondaryCheckboxes({QID}, 6, 3);
        });	
    </script>
    

Minimum elapsed time before moving forward in survey

Tested with: 1.91, IE 7/8, Firefox 7.0, Safari 3.2

This workaround interrupts the Next/Submit function and checks the amount of time elapsed since the page loaded. If the time is less than the minimum dictated, the respondent is alerted and not allowed to proceed. It is useful if you want to enforce a minimum amount of time spent viewing a page.

  1. Set up your survey to use JavaScript.
  2. Place the script below in the source of one of the questions on the page, replacing "TT" with the minimum number of seconds required before advancing is allowed.
<script type="text/javascript" charset="utf-8">

	$(document).ready(function() {

		minTime(TT);

		function minTime(minTime) {

			var startTime = new Date();

			$('#movenextbtn, #movesubmitbtn').click(function(){

				var endTime = new Date();

				if((endTime - startTime)/1000 <= minTime) {
					alert ('You must spend at least '+minTime+' seconds on the question.');
					return false;
				}
				else {
					return true;
				}
			});
		}
	});
 </script>

Minimum number of characters in Long free text or Huge free text questions

Tested with: 1.91, IE 7/8, Firefox 7.0, Safari 3.2

As of version 1.91, you can use regular expression masks (validation) to specify the minimum number of required characters. However, even if the question is mandatory, it can still be blank.
As of version 1.92, if you make the question mandatory and use a validation mask to specify the minimum number of characters, then the user will be forced to answer the question. Note that unlike 1.91, they can submit their current answers, even if partially invalid (so they are stored in the database), but they will not ber able to proceed to the next page until they have passed all mandatory and validity checks.

This workaround interrupts the Next/Submit function and checks the number of characters entered in a Long free text or Huge free text question. If the number is less than the minimum dictated, the respondent is alerted and not allowed to proceed.

  1. Set up your survey to use JavaScript.
  1. Place the script below in the source of one of the questions on the page, replace "QQ" with the question ID and "CC" with the minimum number of characters required before advancing is allowed.
<script type="text/javascript" charset="utf-8">

   $(document).ready(function() {

       minChar(QQ, CC);

       function minChar(qID, minChars) {

           $('#movenextbtn, #movesubmitbtn').click(function(){

               if($('#question'+qID+' textarea').val().replace(/ /g,'').length < minChars) {

                   alert ('You must enter at least '+minChars+' characters.');

                   return false;

               }

               else {

                   return true;

               }

           });

       }

   });

</script>

Variable Length Array (Multi Flexible Text) question

Tested with: 1.90+, IE 6/7/8, FireFox 3.0, Safari 3.2

This workaround allows a respondent to add or remove rows of an Array (Multi Flexible Text) question. It's useful to avoid the page being cluttered with unnecessary array rows.

A function is placed in the template.js file of your template (use template editor). This function hides all of the array rows except the first and inserts two elements that when clicked show or hide rows accordingly.

It must be noted that you can not make this question Mandatory as the respondent will have no way to complete hidden fields. If you want the displayed fields to be mandatory you will need to validate them in another function or modify this function to only allow the addition of rows if all fields in the last row are completed and then disable those fields - that, however, is a little over the top for this example.

Implementation is as follows:

  1. Set up your survey to use JavaScript (See instructions here)
  1. Add the following script to your template.js file.
  1. Replace "QQ" in the function call at the end with the question ID.
  1. Create one or more Array (Multi Flexible Text) questions
  1. If you want to apply it to more arrays on the same page simply add more calls with the appropriate question IDs. (eg. varLengthArray(1); varLengthArray(2);...)


$(document).ready(function() {

   // A function to add or remove rows of an Array (Multi Flexible)(Text) question
	function varLengthArray(qID) {

		if ($('#question'+qID+'').length > 0) {

			// The HTML content of the Add/Remove elements - modify as you wish
			var addContent = '[+]';
			var removeContent = '[-]';

			// Create the Add and Remove elements & insert them
			var el1 = document.createElement('div');
			el1.setAttribute('id','addButton'+qID);
			document.body.appendChild(el1);
			var el2 = document.createElement('div');
			el2.setAttribute('id','removeButton'+qID);
			document.body.appendChild(el2);

			// Move them to after the array
			$( 'div#addButton'+qID ).appendTo($( '#question' + qID + ' table.question' ).parent());
			$( 'div#removeButton'+qID ).appendTo($( '#question' + qID + ' table.question' ).parent());

			// Insert their HTML
			$( 'div#addButton'+qID ).html( addContent );
			$( 'div#removeButton'+qID ).html( removeContent );

			// Style the elements - you can modify here if you wish
			$( 'div#addButton'+qID ).css({
				'margin':'10px 0 0 10px',
				'padding':'1px',
				'text-align':'center',
				'font-weight':'bold',
				'width':'auto',
				'cursor':'pointer',
				'float':'left'
			});

			$( 'div#removeButton'+qID ).css({
				'margin':'10px 0 0 10px',
				'padding':'1px',
				'text-align':'center',
				'font-weight':'bold',
				'width':'auto',
				'cursor':'pointer',
				'float':'left'
			});

			// Initially hide the Remove element
			$( 'div#removeButton'+qID ).hide();

			// Call the functions below when clicked
			$( 'div#addButton'+qID ).click(function (event) {
				addRow(qID);
			});
			$( 'div#removeButton'+qID ).click(function (event) {
				removeRow(qID);
			});

			// Function to add a row, also shows the Remove element and hides the
			//Add element if all rows are shown
			function addRow(qID) {
				var arrayRow = '#question' + qID + ' table.question tbody';
				var rowCount = $( arrayRow ).size() - 1;
				$( arrayRow + '[name="hidden"]:first' ).attr('name', 'visible').show();
				$( 'div#removeButton'+qID ).show();
				if ( $( arrayRow + ':eq(' + rowCount + ')' ).attr('name') == 'visible' )  {
					$( 'div#addButton'+qID ).hide();
				}
			}

			// Function to remove a row, also clears the contents of the removed row,
			// shows the Add element if the last row is hidden and hides the Remove
			// element if only the first row is shown
			function removeRow(qID) {
				var arrayRow = '#question' + qID + ' table.question tbody';
				var rowCount = $( arrayRow ).size() - 1;
				$( arrayRow + '[name="visible"]:last input[type="text"]' ).val('');
				$( arrayRow + '[name="visible"]:last' ).attr('name', 'hidden').hide();
				$( 'div#addButton'+qID ).show();
				if ( $( arrayRow + ':eq(1)' ).attr('name') == 'hidden' )  {
					$( 'div#removeButton'+qID ).hide();
				}
			}

			// Just some initialization stuff
			var arrayRow = '#question' + qID + ' table.question tbody';
			var rowCount = '';

			// Initially hide all except first row or any rows with populated inputs
			$( arrayRow ).each(function(i) {
				if ( i > 0 ) {
					// We also need to give the hidden rows a name cause IE doesn't
					// recognize jQuery :visible selector consistently
					$( this ).attr('name', 'hidden').hide();

					$('input[type=text]', this).each(function(i) {
						if ($(this).attr('value') != '') {
							$(this).parents('tbody:eq(0)').attr('name', 'visible').show();
							$( 'div#removeButton'+qID ).show();
						}
					});
					rowCount = i;
				}
			});
		}
	}

	// Call the function with a question ID
	varLengthArray(QQ);

});

Tested with: Version 3.3.0+180209, Safari 11.0.3

The following code is an update of the above, tested with the latest LS3.

$(document).ready(function() {

   // A function to add or remove rows of an Array (Multi Flexible)(Text) question
	function varLengthArray(qID) {
		
		if ($('#question'+qID+'').length > 0) {
		    
			// The HTML content of the Add/Remove elements - modify as you wish
			var addContent = '[+]';
			var removeContent = '[-]';

			// Create the Add and Remove elements & insert them
			var el1 = document.createElement('div');
			el1.setAttribute('id','addButton'+qID);
			document.body.appendChild(el1);
			var el2 = document.createElement('div');
			el2.setAttribute('id','removeButton'+qID);
			document.body.appendChild(el2);

			// Move them to after the array
			$( 'div#addButton'+qID ).appendTo($( '#question' + qID + ' table.ls-answers' ).parent());
			$( 'div#removeButton'+qID ).appendTo($( '#question' + qID + ' table.ls-answers' ).parent());

			// Insert their HTML
			$( 'div#addButton'+qID ).html( addContent );
			$( 'div#removeButton'+qID ).html( removeContent );

			// Style the elements - you can modify here if you wish
			$( 'div#addButton'+qID ).css({
				'margin':'10px 0 0 10px',
				'padding':'1px',
				'text-align':'center',
				'font-weight':'bold',
				'width':'auto',
				'cursor':'pointer',
				'float':'left'
			});

			$( 'div#removeButton'+qID ).css({
				'margin':'10px 0 0 10px',
				'padding':'1px',
				'text-align':'center',
				'font-weight':'bold',
				'width':'auto',
				'cursor':'pointer',
				'float':'left'
			});

			// Initially hide the Remove element
			$( 'div#removeButton'+qID ).hide();

			// Call the functions below when clicked
			$( 'div#addButton'+qID ).click(function (event) {
				addRow(qID);
			});
			$( 'div#removeButton'+qID ).click(function (event) {
				removeRow(qID);
			});

			// Function to add a row, also shows the Remove element and hides the
			//Add element if all rows are shown
			function addRow(qID) {
				var arrayRow = '#question' + qID + ' table.ls-answers tr.subquestion-list';
				var rowCount = $( arrayRow ).size() - 1;
				$( arrayRow + '[name="hidden"]:first' ).attr('name', 'visible').show();
				$( 'div#removeButton'+qID ).show();
				if ( $( arrayRow + ':eq(' + rowCount + ')' ).attr('name') == 'visible' )  {
					$( 'div#addButton'+qID ).hide();
				}
			}

			// Function to remove a row, also clears the contents of the removed row,
			// shows the Add element if the last row is hidden and hides the Remove
			// element if only the first row is shown
			function removeRow(qID) {
				var arrayRow = '#question' + qID + ' table.ls-answers tr.subquestion-list';
				var rowCount = $( arrayRow ).size() - 1;
				$( arrayRow + '[name="visible"]:last input[type="text"]' ).val('');
				$( arrayRow + '[name="visible"]:last' ).attr('name', 'hidden').hide();
				$( 'div#addButton'+qID ).show();
				if ( $( arrayRow + ':eq(1)' ).attr('name') == 'hidden' )  {
					$( 'div#removeButton'+qID ).hide();
				}
			}

			// Just some initialization stuff
			var arrayRow = '#question' + qID + ' table.ls-answers tr.subquestion-list';
			var rowCount = '';

			// Initially hide all except first row or any rows with populated inputs
			$( arrayRow ).each(function(i) {
				if ( i > 0 ) {
					// We also need to give the hidden rows a name cause IE doesn't
					// recognize jQuery :visible selector consistently
					$( this ).attr('name', 'hidden').hide();

					$('input[type=text]', this).each(function(i) {
						if ($(this).attr('value') != '') {
							$(this).parents('tbody:eq(0)').attr('name', 'visible').show();
							$( 'div#removeButton'+qID ).show();
						}
					});
					rowCount = i;
				}
			});
		}
	}

	// Call the function with a question ID
	varLengthArray(QQ);

});

LimeSurvey version 6.x

This is an updated script for version 6.x which also handles sub-question relevance:

$(document).ready(function() {

   // A function to add or remove rows of an Array (Multi Flexible)(Text) question
	function varLengthArray(qID) {
		
		if ($('#question'+qID+'').length > 0) {
			
			var thisQuestion = $('#question'+qID);
			
			// The HTML content of the Add/Remove elements - modify as you wish
			var addContent = '[+] Add row';
			var removeContent = '[-] Remove row';
			
			// The classes for the Add/Remove elements - modify as you wish
			// See https://getbootstrap.com/docs/5.0/getting-started/introduction/
			var addClasses = 'inserted-button add btn btn-success';
			var removeClasses = 'inserted-button remove btn btn-danger';
			
			// The styles for the Add/Remove elements - modify as you wish
			// These could be placed in your custom.css file.
			var addStyles = 'margin:10px 0 10px 10px; padding:1px 5px; text-align:center; width:auto; cursor:pointer; float:left;';
			var removeStyles = 'margin:10px 0 10px 10px; padding:1px 5px; text-align:center; width:auto; cursor:pointer; float:left;';
			
			// Insert the buttons
			$( 'table.ls-answers', thisQuestion).after('<div class="button-wrapper">\
					<button type="button" class="'+addClasses+'" style="'+addStyles+'">'+addContent+'</button>\
					<button type="button" class="'+removeClasses+'" style="display: none; '+removeStyles+'">'+removeContent+'</button>\
				</div>');

			// Listeners on the buttons
			$('.inserted-button.add', thisQuestion).on('click', function (event) {
				addRow();
			});
			$('.inserted-button.remove', thisQuestion).on('click', function (event) {
				removeRow();
			});
			
			// Define the relevant rows
			var relevantRows = $('tr.subquestion-list:not(.ls-irrelevant)', thisQuestion);

			// Function to add a row, show the "Remove" element and hide the "Add" element if all rows are shown
			function addRow() {
				$('[data-visible="false"]:first', thisQuestion).attr('data-visible', 'true').show();
				$('.inserted-button.remove', thisQuestion).show();
				console.log($('[data-visible="true"]', thisQuestion).length+' == '+$(relevantRows).length - 1);
				if ($('[data-visible="false"]', thisQuestion).length == 0)  {
					$('.inserted-button.add', thisQuestion).hide();
				}
				$('.inserted-button.add', thisQuestion).blur();
			}

			// Function to remove a row, clear the contents of the removed row,
			// show the "Add" element if the last row is hidden and hide the "Remove" element if only the first row is shown
			function removeRow() {
				$('[data-visible="true"]:last input:text', thisQuestion).val('').trigger('keyup');
				$('[data-visible="true"]:last', thisQuestion).attr('data-visible', 'false').hide();
				$('.inserted-button.add', thisQuestion).show();
				if ($('[data-visible="true"]', thisQuestion).length == 0)  {
					$('.inserted-button.remove', thisQuestion).hide();
				}
				$('.inserted-button.remove', thisQuestion).blur();
			}

			// Initially hide all except first row or any rows with populated inputs
			$(relevantRows).slice(1).each(function(i) {
				$( this ).attr('data-visible', 'false').hide();

				$('input[type=text]', this).each(function(i) {
					if ($.trim($(this).val()) != '') {
						$(this).closest('tr').attr('data-visible', 'true').show();
						$('.inserted-button.remove', thisQuestion).show();
					}
				});
			});
		}
	}
	// Call the function with a question ID
	varLengthArray(QQ);
});

Expandable Array

Tested with: 1.90+, IE 6/7, FireFox 3.5, Safari 3.2

This workaround allows you to present an array question incrementally. That is to say that initially only the first option (row) in the array is shown. When that option is answered the next is shown and so-on.

A JavaScript function is placed in the source of the array question. This function hides all of the array rows except the first and then displays them as options are answered.

The workaround supports following question types:

  • Array (flexible labels)
  • Array (flexible labels) by column
  • Array (Yes/No/Uncertain)
  • Array (Increase, Same, Decrease)
  • Array (Flexible Labels) dual scale
  • Array (10 point choice)

Implementation is as follows:

  1. Set up your survey to use JavaScript. (See documentation here)
  2. Create one or more Array questions
  3. In the source of the first array question add the following script.
  4. Replace "QQ" in the function call at the end of the script with the question ID of the array you would like to manipulate.
  5. If you want to apply it to more arrays on the same page, simply add more calls with the appropriate question IDs. (eg. expandingArray(1); expandingArray(2);...)
<script type="text/javascript">

	$(document).ready(function() {

		// A function to show subsequent rows of an array as options are checked
		function expandingArray(qID) {

			// Build an array of the question rows
			var arrayRow = '#question' + qID + ' table.question tbody tr';

			// Initially hide all rows unless an input was previously checked
			$( arrayRow ).each(function(i) {

				if ( $( arrayRow  + ':eq(' + i + ') input.radio:checked' ).length != 0 ) {
					$(this).attr('name', 'clickedRow');
				}
				else {
					$(this).attr('name', 'hidden').hide();
				}
			});

			// Now show the first hidden row
			addRow();

			// Add another row when an option is checked for the first time
			$( '#question' + qID + ' td.answer input.radio' ).click(function (event) {

				if ($(this).parents('tr:eq(0)').attr('name') != 'clickedRow') {
					addRow();
					$(this).parents('tr:eq(0)').attr('name', 'clickedRow');
				}

				// The original function of the click event
				checkconditions(this.value, this.name, this.type);
			});

			// Add another row when an table cell is clicked for the first time
			$( '#question' + qID + ' table.question tbody td' ).click(function (event) {

				if ($(this).parents('tr:eq(0)').attr('name') != 'clickedRow') {
					addRow();
					$(this).parents('tr:eq(0)').attr('name', 'clickedRow');
				}
			});

			// Function to add a row
			function addRow() {
				$( arrayRow + '[name="hidden"]:first' ).attr('name', 'visible').show();
				// Now, scroll down
				$("html, body").animate({ scrollTop: $(document).height() }, 1000);
			}
		}

		// Call the function with a question ID
		expandingArray(QQ);

	});
</script>

Partially Randomized Answers - Array questions

Tested with: 2.05

This workaround allows the answers of Array questions to be randomized while always keeping one subquestion at the end of the list.

After the LimeSurvey randomizing has occurred a function in the source of a question moves the subquestion with a defined code to the bottom of the array.

Implementation is as follows:

  1. Set up your survey to use JavaScript
  2. Create an Array type question.
  3. Set the "Random order" question setting to "Randomize on each page load".
  4. Place the following script in the source of the question.
  5. Modify the "fixedCode" value as required.


<script type="text/javascript" charset="utf-8">	
	$(document).ready(function() {
 
		// The subquestion code to place in the last position
		var fixedCode = 'A1';
 
		// Identify this question
		var q1ID = {QID};
		var thisQuestion = $('#question'+q1ID);
 
		// Move the "fixed" row to the end
		$('table.subquestion-list tbody', thisQuestion).append($('tr[id$="X'+q1ID+fixedCode+'"]'));
			
		// Fix up the array row background colours
		$('tr.answers-list', thisQuestion).each(function(i){
			$(this).removeClass('array1 array2').addClass('array'+(2-(i%2)));
		});
 
    });
</script>

Download sample survey:
Partially_randomized_array.lss

Partially Randomized Answers - Multiple Options & List (radio) questions

Tested with: 2.05

This workaround allows the answers of Multiple Options & List (radio) questions to be randomized while always keeping one answer at the end of the list. It's useful if you want to randomize, say, the first 4 of your answers but always have the fifth displayed last as in the image below:

After the LimeSurvey randomizing has occurred a function in the source of a question moves the answer with the highest answer code to the end of the list.

Some conditions on the use of this are:

  • The "Show No answer" survey setting must be set to "No"
  • You must use sequential numbers, starting at 1 as response codes. (See image below)

Implementation is as follows:

  1. Set up your survey to use JavaScript
  2. Create a Multiple Options or List (radio) question with sequential response codes.
  3. Set the "Random order" question setting to "Randomize on each page load".
  4. Place the following script in the source of the question


<script type="text/javascript" charset="utf-8">	
 
	$(document).ready(function() {
 
		// Identify this question
		var qID = {QID}; 
 
		// Find the number of answers
		var ansCount = $('#question'+qID+' li.answer-item').length;
 
		// Place the last answer created at the end of the list
		var answer = $( 'input[id$="X'+qID+ansCount+'"]');
		var answerItem = $(answer).closest('li');
		var answersList = $(answer).closest('ul');
		$(answersList).append(answerItem);
 
	});
</script>

Download sample survey:
Partially_Ranomized_Answers.lss

Partially Randomized Answers - Multiple Options & List (radio) questions (Enhanced)

Tested with LimeSurvey versions 2.06, 2.73.0, 3.6.2

This workaround allows the answers of Multiple Options & List (radio) questions to be randomized while always keeping a specified number of answers fixed at the end of the list. It's useful if you want to randomize, say, the first 4 of your answers but always have the fifth and sixth displayed last as in the image below:

After the LimeSurvey randomizing has occurred, a function in the source of a question moves the answers with the highest answer codes to the end of the list.

Some conditions on the use of this are:

  • The "Show No answer" survey setting must be set to "No"
  • You must use sequential numbers, starting at 1 as response codes. (See image below)

LimeSurvey version 2.06

  1. Set up your survey to use JavaScript
  2. Create a Multiple Options or List (radio) question with sequential response codes.
  3. Set the "Random order" question setting to "Randomize on each page load".
  4. Place the following script in the source of the question.
  5. Modify the "fixedAnswers" and "otherFixed" variables as required.


<script type="text/javascript" charset="utf-8">	
	
	$(document).ready(function() {
	
		// The number of answers to be fixed at the end of the list
		var fixedAnswers = 2;

		// Set this to "true" if you want "Other" to be fixed in the last position
		var otherFixed = false;
		
		// Identify this question
		var qID = {QID}; 
		
		// Find the number of answers
		var ansCount = $('#question'+qID+' li.answer-item').length;
		if($('#question'+qID+' input[type="text"]').length > 0) {
			ansCount = ansCount -1
		}
		
		// Place the last n answers created at the end of the list
		var fixedIndex = fixedAnswers - 1;
		for (var i=0; i<fixedAnswers; i++) {
			var answer = $( 'input[id$="X'+qID+(ansCount-fixedIndex)+'"]');
			var answerItem = $(answer).closest('li');
			var answersList = $(answer).closest('ul');
			$(answersList).append(answerItem);
			fixedIndex--;
		}		
		
		// Handle "Other"
		if(otherFixed == true && $('#question'+qID+' input[type="text"]').length > 0) {
			var otherAnswer = $('#question'+qID+' input[type="text"]');
			var otherAnswerItem = $(otherAnswer ).closest('li');
			var otherAnswersList = $(otherAnswer ).closest('ul');
			$(otherAnswersList).append(otherAnswerItem);
		}		
	});
</script>



LimeSurvey version 2.73.0:

  1. Set up your survey to use JavaScript
  2. Create a Multiple Options or List (radio) question with sequential response codes.
  3. Set the "Random order" question setting to "Randomize on each page load".
  4. Place the following script in the source of the question.
  5. Modify the "fixedAnswers" and "otherFixed" variables as required.


<script type="text/javascript" charset="utf-8">	
	
	$(document).ready(function() {
	
		// The number of answers to be fixed at the end of the list
		var fixedAnswers = 2;

		// Set this to "true" if you want "Other" to be fixed in the last position
		var otherFixed = false;
		
		// Identify this question
		var qID = {QID}; 
		
		// Find the number of answers
		var ansCount = $('#question'+qID+' .answer-item').length;
		if($('#question'+qID+' input[type="text"]').length > 0) {
			ansCount = ansCount -1
		}
		
		// Place the last n answers created at the end of the list
		var fixedIndex = fixedAnswers - 1;
		for (var i=0; i<fixedAnswers; i++) {
			var answer = $('input[id^="answer"][id$="X'+qID+'s'+(ansCount-fixedIndex)+'"]');
			var answerItem = $(answer).closest('.answer-item');
			var answersList = $(answer).closest('.answers-list');
			if($('#question'+qID).hasClass('multiple-opt')) {
				answer = $('input[id^="answer"][id$="X'+qID+(ansCount-fixedIndex)+'"]');
				answerItem = $(answer).closest('.answer-item').parent();
				answersList = $(answer).closest('.subquestion-list');
			}
			$(answersList).append(answerItem);
			fixedIndex--;
		}		
		
		// Handle "Other"
		if(otherFixed == true && $('#question'+qID+' input[type="text"]').length > 0) {
			var otherAnswer = $('#question'+qID+' input[type="text"]');
			var otherAnswerItem = $(otherAnswer ).closest('.answer-item');
			var otherAnswersList = $(otherAnswer ).closest('.answers-list');
			if($('#question'+qID).hasClass('multiple-opt')) {
				otherAnswerItem = $(otherAnswer ).closest('.answer-item').parent();
				otherAnswersList = $(otherAnswer ).closest('.subquestion-list');
			}
			$(otherAnswersList).append(otherAnswerItem);
		}
	});
</script>



LimeSurvey version 3.x:

  1. Set up your survey to use JavaScript
  2. Create a Multiple Options or List (radio) question with sequential response codes.
  3. Set the "Random order" question setting to "Randomize on each page load".
  4. Place the following script in the source of the question.
  5. Modify the "fixedAnswers" and "otherFixed" variables as required.


<script type="text/javascript" charset="utf-8">	
 
	$(document).on('ready pjax:scriptcomplete',function(){
 
		// The number of answers to be fixed at the end of the list
		var fixedAnswers = 2;
 
		// Set this to "true" if you want "Other" to be fixed in the last position
		var otherFixed = false;
 
		// Identify this question
		var qID = {QID}; 
 
		// Find the number of answers
		var ansCount = $('#question'+qID+' .answer-item').length;
		if($('#question'+qID+' input[type="text"]').length > 0) {
			ansCount = ansCount -1
		}
		console.log(ansCount);
 
		// Place the last n answers created at the end of the list
		var fixedIndex = fixedAnswers - 1;
		for (var i=0; i<fixedAnswers; i++) {
			var answer = $('input[id^="answer"][id$="X'+qID+(ansCount-fixedIndex)+'"]');
			var answerItem = $(answer).closest('.answer-item');
			var answersList = $(answer).closest('ul');
			$(answersList).append(answerItem);
			fixedIndex--;
		}		
 
		// Handle "Other"
		if(otherFixed == true && $('#question'+qID+' input[type="text"]').length > 0) {
			var otherAnswer = $('#question'+qID+' input[type="text"]');
			var otherAnswerItem = $(otherAnswer ).closest('.answer-item');
			var otherAnswersList = $(otherAnswer ).closest('ul');
			$(otherAnswersList).append(otherAnswerItem);
		}
	});
</script>

Partially Randomized Answers - List (dropdown) questions

Tested with: 1.90+, IE 6/7, FireFox 3.0, Safari 3.2

This workaround, similar to the one above, allows the answers of List (dropdown) questions to be randomized while always keeping one answer at the end of the list. It's useful if you want to randomize, say, the first 4 of your answers but always have the fifth displayed last.

A script is placed in the source of a question. After the randomizing has occurred this script moves the answer with the highest answer code to the end of the list.

A condition is that you must use sequential numbers, starting at 1 as response codes. (See image below)

Implementation is as follows:

  1. Turn off $filterxsshtml to allow insertion of JavaScript in the questions. (See documentation here)
  2. Create one or more List (dropdown) questions with sequential response codes. Set the random_order question attribute to 1.
  3. In the source of the first question add the following onload function. (See How to use script here)
  4. Replace the ID question at the end of the function call.
<script type="text/javascript" charset="utf-8">

	$(document).ready(function() {

		// Function to allow randomization of all answers
		//except the last one in List/dropdown questions
		function partRandDD(qID) {

			// Find the last answer option
			var ansCount = $( 'div#question' + qID + ' select option' ).length - 1;

			// Place the last answer option at the end of the list
			$( 'div#question' + qID + ' select option[value="' + ansCount + '"]' )
				.appendTo($( 'div#question' + qID + ' select' ));
		}

		// Call the function with the qID (replace the qID eg. partRandDD(244)) 
		partRandDD(qID);
		//If you want to apply it to more questions on the same page simply add more calls with the appropriate IDs. 
		partRandDD(qID);
     });
</script>

Partially Randomized Answers - Multiple numerical input

Tested with: 1.90+, Safari 5.1

This workaround, similar to the ones above, allows the answers of Multiple numerical input questions to be randomized while always keeping one answer at the end of the list. It's useful if you want to randomize, say, the first 4 of your answers but always have the fifth displayed last.

An onload function is placed in the source of a question. After the randomizing has occurred this function moves the answer with the highest answer code to the end of the list.

A condition is that you must use sequential numbers, starting at 1 as response codes. (See image below)

Implementation is as follows:

  1. Turn off $filterxsshtml to allow insertion of JavaScript in the questions. (See documentation here)
  2. Create one or more Multiple numerical input question with sequential response codes. Set the random_order question attribute to 1.
  3. In the source of the first question add the following onload function. (See How to use script here)
  4. Replace "SSSSS", "GG", "QQ" in the function call at the end with the survey ID, group ID and question ID respectively.
  5. If you want to apply it to more questions on the same page simply add more calls with the appropriate IDs. (eg. partRandDD(11111, 22, 1); partRandDD(11111, 22, 2); ...)
<script type="text/javascript" charset="utf-8">

	$(document).ready(function() {

		// Function to allow randomization of all answers except the last one in multiple numeric input questions
		function partRand(sID, gID, qID) {

			// Find the number of answers
			var ansCount = ''
			$( 'div#question' + qID + ' div.answers li' ).each(function(i) {
				ansCount = (i - 1);
			});

			// Place the last answer created at the end of the list - but before the first helping sum
			$( 'li#javatbd' + sID + 'X' + gID + 'X' + qID + ansCount + '' ).insertBefore($( 'div#question' + qID + ' div.answers li.multiplenumerichelp' ).first());

		}

		// Call the function with the SID, GID and QID
		partRand(SID, GID, QID);

	});

</script>

Using this solution for Multiple numerical input questions might be a bit more complicated than for other question types. All answers are organized in an unordered list (<ul>) structure. However, if advanced question settings like Equals sum value are used, LimeSurvey adds helping information (e.g. sum of all entries and remainder) to the same unordered list using the class multiplenumerichelp for these additional list items. The above example assumes that at least one of these list items is present and moves the last answer to the position right above the first helper item. Without such advanced settings, the code will most certainly need adaptation.

Making one (or more) rows of an array mandatory

 Hint: With Expression manager, you can use Using em_validation_q in array system.


Tested with: 1.90+, IE 6/7, Firefox 3.0, Safari 3.2

This workaround allows you to define one or more rows of an array question as mandatory (instead of all of them which the "Mandatory" question setting does). It is demonstrated here.

A JavaScript function interrupts the submit function and checks if the inputs in the mandatory row(s) are populated. If not, an alert is popped up, the offending inputs are turned pink and the submit is aborted. Otherwise the submit goes ahead.

Implementation is as follows:

  1. Turn off $filterxsshtml to allow insertion of JavaScript in the questions (see documentation here).
  2. Create the array question.
  3. In the source of the array question add the following script (see How to use script here).
  4. Modify the warningText (line10)as required.
  5. Replace "QQ" (lines 13 and 14) with the question ID and add calls as necessary for more rows - the example code renders rows 1 and 3 mandatory.
<script type="text/javascript" charset="utf-8">

	$(document).ready(function () {

		// Interrupt the submit function
		$('form#limesurvey').submit(function () {

			// Override the built-in "disable navigation buttons" feature
			$('#moveprevbtn, #movenextbtn, #movesubmitbtn').attr('disabled', '');

			var empty = 0;
			var warningText = 'Please complete the highlighted inputs.';

			// Call the mandatory row function with question IDs and row numbers
			mandatoryRow(QQ, 1);
			mandatoryRow(QQ, 3);

			// A function to render rows of an array mandatory
			function mandatoryRow(qID, rowNum) {

				$('div#question' + qID + ' table.question tbody[id^="javatbd"]:eq(' + Number(rowNum - 1) + ') input[type="text"]').each(function (i) {

					if ($(this).val() == '') {
						$(this).css('background-color', 'pink');
						empty = 1;
					} 
					else {
						$(this).css('background-color', '#FFFFFF');
					}
				});
			}

			if (empty == 1) {
				alert(warningText);
				return false;
			} 
			else {
				return true;
			}
		});

	});

</script>

Randomly displaying one of a few YouTube videos, and recording which was displayed

Tested in 1.85+

Jason Cleeland has created and documented a method to randomly display one of a number of youtube videos in a single question, and save which one was displayed.

http://www.aptigence.com.au/home/archives/23-Random-display-of-videos-in-a-question-2-YouTube.html

Randomly displaying one of a few pictures, and recording which was displayed

Tested in 1.91+

The following steps allow you to randomly display one of a few pictures and record in the dataset which picture was displayed. We use four pictures in this example, but you can easily adapt these scripts according to your needs.

1. Create a new sub-directory pic in your LimeSurvey folder.

2. Copy four pictures (named 1.jpg, 2.jpg, 3.jpg, and 4.jpg) in this pic sub-directory.

3. Deactivate the Filter HTML for XSS function in LimeSurvey's Global Settings.

4. Create a Short free text question where you want these pictures to be displayed.

5. Figure out the SGQA Identifier of this question (e.g., 12345X67X89).

6. Paste the following code as question text after activating the editor's Source view and replace both SSSSSXGGXQQ strings with your SGQA identifier:

Please have a look at this picture:

<br />

<script>

$(document).ready(

function(){

// Find a random number (here between 1 and 4)

var randNumber = Math.floor(Math.random()*4 + 1);

// Save the number as answer of this question

$('input#answerSSSSSXGGXQQ').val(randNumber);

// Hide this answer field

document.getElementById("answerSSSSSXGGXQQ").style.display='none';

// Show the picture

picString = '<img src="pic/' + String(randNumber) + '.jpg">';

document.getElementById('pic').innerHTML = picString;

}

);</script>

<p>

    </p>

<div id="pic">

    </div>

If you want to display the number of this picture in a later question, use the following code as question text (don't forget to replace the SSSSSXGGXQQ string acordingly):

You saw picture number {INSERTANS:SSSSSXGGXQQ}.

Perhaps you want to display the same picture again in a later question, but a little bit smaller (100px width, 100px height)? Just paste the following code as question text after activating the editor's Source view, and replace the SSSSSXGGXQQ string acordingly:

Here you see the picture again: <br>

<script>

$(document).ready(

function(){

// Get the picture number from previous question

var randNumber = "{INSERTANS:SSSSSXGGXQQ}";

// Show picture

picString = '<img width="100" height="100" src="pic/' + String(randNumber) + '.jpg">';

document.getElementById('pic').innerHTML = picString;

}

);</script>

<div id="pic">

 </div>

Multiple numerical input with max_num_value defined in token (personalized limit)

Tested in 1.85+

As of version 1.92, max_num_value can be an experession, so you can simply enter the {TOKEN} or other variable in that question attribute.

To make personalized survey with multiple numerical input when max_num_value (Maximum sum value) is different for each user and is loaded from the token use following tricks:

  • Create 1st group with numerical input field (1000X10X11). "Limit"
  • Add java script into this question:
<script type="text/javascript" charset="utf-8">

function Custom_On_Load(){

  document.getElementById('answer1000X10X11').value='{TOKEN:ATTRIBUTE_1}';

  document.getElementById('answer1000X10X11').readOnly=1;

}

</script>

where:

TOKEN:ATTRIBUTE_1 is individual limit defined in token.

readOnly=1 // user shouldn't change it.

  • Create 2nd group with multiple numerical input and add "Max value from SGQA" with 1000X10X11 as argument.

Note: If you don't want to show first field with numerical input (and show definied limit in different way) you can add 2 lines to above script to hide whole question:

document.getElementById('question85').style.display='none';

document.getElementById('display85').value='';

where:

question85 and display85 are field names.

Note: It's no possible to create it into one group, because all SGQA references can only refer to data on a previous page because when clicking next the answer is stored at the DB and the SGQA is replaced by querying the database.

Default values in array questions

Tested with: 2.05+

This workaround uses JavaScript to allow you to pre-check default answers in all subquestions of an array type question. You can select a column of the array to be checked as the page loads. The script first checks to see if a subquestion has already been answered and, if not, checks the default answer.

Implementation is as follows:

  1. Set up your survey to use script.
  2. Create the array question.
  3. In the source of the array question add the following script.
  4. Replace"QQ" with the question ID and "CC" with the column number to be checked.
<script type="text/javascript" charset="utf-8">	
	$(document).ready(function() {
		// A function to pre-check a column of an array
		function checkedDefault(qID, column) {
			var checkedCol = column - 1;
			$('#question'+qID+' tr.subquestions-list').each(function(i) {
				if ($('input.checkbox:checked', this).length == 0) {
					$('input.checkbox:eq('+checkedCol+')', this).prop('checked', true);
					$('input.checkbox:eq('+checkedCol+')', this).parent().find('input[type="hidden"]').val(1);
				}
			});
		}
		// Call the function with a question ID and column number
		checkedDefault(QQ, CC);
	});

</script>

Default Values In One Scale Of A Dual-scale Array Question

Tested with: 2.0+, IE 9/10, Firefox 24

Overview
This workaround uses JavaScript to allow you to pre-check default answers in all subquestions of one scale of a dual-scale-array type question. You can select a scale and a column of that scale to be checked. The script first checks to see if a subquestion has already been answered and, if not, checks the default answer.

Implementation:

  1. Set up your survey to use script.
  2. Create the array question.
  3. In the source of the array question add the following script.
  4. Adjust "defaultAnsweredScale" and "defaultAnsweredColumn" as necessary.


<script type="text/javascript" charset="utf-8">	
 
	$(document).ready(function() {
  
		// Scale to set the default answer on
		var defaultAnsweredScale = 2; 
		// Column of that scale to set as default answer
		var defaultAnsweredColumn = 3;
		
		// Identify this question
		var thisQuestion = $('#question{self.qid}');
		
		// Identify the scales and columns
		$('.answer-item[class^="answer_cell_1"]', thisQuestion).addClass('scale-1-item');
		$('.answer-item[class^="answer_cell_2"]', thisQuestion).addClass('scale-2-item');
		$('tr.answers-list', thisQuestion).each(function(i) {
			$('.scale-1-item', this).each(function(i) {
				$(this).addClass('scale-1-column-'+(i+1)+'-item');
			});
			$('.scale-2-item', this).each(function(i) {
				$(this).addClass('scale-2-column-'+(i+1)+'-item');
			});
		});
		
		// Click the default answers
		$('tr.answers-list', thisQuestion).each(function(i) {
			if($('.scale-'+defaultAnsweredScale+'-item input[type="radio"]:checked', this).length == 0) {
				$('.scale-'+defaultAnsweredScale+'-column-'+defaultAnsweredColumn+'-item input[type="radio"]:last', this).click();
			}
		});
	});
</script>


Download sample survey:
Dual_scale_with_default_answers.lss

Record group view time

Tested with: 1.87+ (8518), IE 6/7, Firefox 3.5, Safari 3.2

As of version 1.92, group view time is accurately recorded in the timings table if you choose the Record Timings survey option.

This workaround uses JavaScript to populate a hidden question with the total time that a respondent views a group. If used in a group with only one question, it could be used to track the amount of time required to answer the question.

Implementation is as follows:

  1. Set up your survey to use JavaScript.
  2. In the group, create a short-text question. We will hide this later with JavaScript and populate it with the elapsed time.
  3. Place the following script in the source of the short-text question.
  4. Replace "QQ" in line 6 with the ID of the short-text question.

The script populates the hidden question with the elapsed time since the group was opened. If a respondent leaves the group and then returns, the timer continues up from the elapsed time of the last visit.

<script type="text/javascript" charset="utf-8">

   $(document).ready(function(){

       // call the functions with the hidden timer question ID

       runTimer(QQ);

       function runTimer(timeQID) {

           $('#question'+timeQID).hide();

           var initSec = '0';

           // Check for elapsed time from previous visits

           if ($('#question'+timeQID+' input.text').val()) {

               var initTime = $('#question'+timeQID+' input.text').val();

               initTimeArr = initTime.split(':');

               initSec = Number[[initTimeArr[0]*3600]]+Number[[initTimeArr[1]*60]]+Number(initTimeArr[2]);

           }

           // Run the timer update function

           recordTime(initSec, timeQID);

       }

   });

   // Timer update function

   function recordTime(elapsedSec, timeQID){

       elapsedSec++;

       var h = Math.floor(elapsedSec / 3600);

       var m = Math.floor(elapsedSec % 3600 / 60);

       var s = Math.floor(elapsedSec % 3600 % 60);

       var elapsedTime = [[h > 0 ? h + ":" : "00:") + (m > 0 ? (m < 10 ? "0" : "") + m + ":" : "00:") + (s < 10 ? "0" : "") + s);

        $('#question'+timeQID+' input.text').val(elapsedTime);

        // Run the timer update function every second

        setTimeout('recordTime('+elapsedSec+', '+timeQID+')',1000);

    }

 </script>

Toggle visibility of groups

Tested with: 1.90+, IE 6/7/8, Firefox 3.6, Safari 3.2

This workaround uses JavaScript to toggle the visibility of groups in an "all in one" survey. Initially the groups are hidden and there are links inserted to display the groups. It's useful to avoid the page being cluttered with many groups and lets the respondent focus on one at a time.

It's demonstrated here.

Implementation is as follows:

  1. Set up your survey to use JavaScript.
  2. Set the survey to run in "all in one" mode.
  3. Add your groups and questions.
  4. Place the following script in the source of one of the questions in the first group.


The comments in the script are pretty self-explanatory but it does the following:

  1. initially hides all of the groups
  2. inserts a link above each one to toggle its display
  3. some styles are added to the clickable links but these could be done in template.css instead
  4. shows any groups that have an unanswered mandatory after a submit attempt


<script type="text/javascript" charset="utf-8">

	$(document).ready(function(){

		// Insert show/hide divs before all group wrapper divs
		$('<div class="groupToggler"><span></span></div>').insertBefore('div[id^="group-"]');
		
		// Add some text to the show/hide divs
		$('.groupToggler span').each(function(i) {
			($( this ).text('Show/Hide Group '+(i+1)+''));
		});
		
		// Add some styles to the show/hide divs
		$('.groupToggler').css({
			'padding':'5px 0 10px 0',
			'text-align':'center'
		});
		$('.groupToggler span').css({
			'text-decoration':'underline',			
			'cursor':'pointer'
		});
		
		// Add some hover effects to the show/hide divs
		$(".groupToggler span").hover(
			function () {
				$(this).css({
					'text-decoration':'none',
					'font-weight':'bold'
				});
			}, 
			function () {
				$(this).css({
					'text-decoration':'underline',
					'font-weight':'normal'
				});
			}
		);
		
		// Initially hide all of the groups
		// Toggle their visibility when the show/hide is clicked
		$('.groupToggler span').click(function() {
			$(this).parent().next().toggle();
			return false;
		}).parent().next().hide();
		
		// Display any groups that have unanswered mandatories
		$('.errormandatory').parents('div[id^="group-"]').show();
		
		// Some tidying up
		$('h1').next().next().hide();
		$('.surveywelcome').css({
			'margin-bottom':'10px'
		});
		$('.surveywelcome').next().hide();
		$('.surveywelcome').next().next().hide();

	});

</script>

Use jQuery Autocomplete plugin to suggest answers for text inputs

1.91 and later versions

Tested with: 1.91+, IE 6/7/8, Firefox 3.6, Safari 3.2

This workaround uses the Autocomplete widget included with jQuery UI to provide a respondent a list of suggested answers that the they can select from as they type in a text input.

Although the plugin allows for much more complex applications, the two that I use most frequently are outlined below. The first method uses a relatively small list of suggestions that is stored in an array locally. The second is a little more complex - it uses a remote CSV file of suggestions that is accessed through AJAX an PHP.

Both methods:

  • Add the following styles to the end of your 'template.css' file
/* Autocomplete styles */

ul.ui-autocomplete {
	width: 250px !important;
	padding: 5px 10px;
	list-style: none;
}

Method 1 (use local JavaScript array for data):

  • Create a text input question
  • Add the following script to the source of the text question
  • Replace "QQ" with the ID of the text input question
  • Replace the states with your list of suggestions (comma separated)
<script type="text/javascript" charset="utf-8">

	$(document).ready(function() {

		var q1ID = 'QQ';

		var states = "Alabama,Alaska,Arizona,Arkansas,California,Colorado,Connecticut,Delaware, District of Columbia,Florida,Georgia,Hawaii,Idaho,Illinois,Indiana,Iowa,Kansas,Kentucky, Louisiana,Maine,Montana,Nebraska,Nevada,New Hampshire,New Jersey,New Mexico,New York, North Carolina,North Dakota,Ohio,Oklahoma,Oregon,Maryland,Massachusetts,Michigan, Minnesota,Mississippi,Missouri,Pennsylvania,Rhode Island,South Carolina,South Dakota, Tennessee,Texas,Utah,Vermont,Virginia,Washington,West Virginia,Wisconsin,Wyoming".split(',');

		$('#question'+q1ID+' input.text').autocomplete({
			source: states
		});

	});

</script>

Method 2 (use remote CSV file for data):

  • Place the CSV file of suggestions in your template directory
  • Place a PHP file containing the code below in your template directory
  • Replace "countries2.csv" with your CSV filename (you could make this more universal by passing the filename in the URL)
  • This file will grab the contents of the CSV and encode them in JSON format that the JavaScript can use
  • Place the JavaScript code below in ths source of the text question
  • Replace "QQ" with the ID of the text input question
  • Replace "templates/yourTemplate/countries.php" with the path to the PHP file (from LimeSurvey root)
  • This script will grab the jsonized data from the PHP file and load it into an array that the plugin can use

PHP code:

<?php

	$countriesArr = array();

	$file_handle = fopen("countries2.csv", "r");

	while (!feof($file_handle) ) {
		$line_of_text = fgetcsv($file_handle);
		array_push($countriesArr, $line_of_text[0]);
	}

	fclose($file_handle);

	echo json_encode($countriesArr);

?>

JavaScript code:

<script type="text/javascript" charset="utf-8">

	$(document).ready(function() {

		var qID = QQ;
		var url = "templates/yourTemplate/countries.php";
		var dataArr = [];

		$.getJSON(url,function(data){

			$.each(data,function(i, item){
				dataArr.push(item);
			});

			$('#question'+qID+' input.text').autocomplete({
				source: dataArr
			});

		});

	});

</script>


Update for LimeSurvey 2.05+

Tested with: 2.05+, Chrome 40, FireFox 36, Opera 27, IE 11

With LimeSurvey 2.05+ it is NOT necessary to:

  1. Download the jQuery Autocomplete widget
  2. Add special autocomplete styling to the survey template (tested with the Skeletonquest template by Denis Chenu)

This workaround builds on the previous examples, but is updated for use with newer versions of LimeSurvey. Note that the Autocomplete widget is already included in LimeSurvey, as part of jQuery UI.


Autocomplete may be configured to use different types of data input, three of which will be demonstrated in the following: Plain text, CSV-files, and MySQL databases.


1. Plain text input (the easiest, suitable for small lists or arrays).

To enable autocomplete of a list in plain text, simply add the following code to your question source. You will have to change qID to the question ID of the question you want the autocomplete to run in (this does not have to be the one you add the script to). In this case, the list has been added to the source directly, but you may also call the list as a predefined variable (see jQuery website for example):

<script type="text/javascript" charset="utf-8">
 
    $(document).ready(function() {
 
		var qID = 1;
 
		$('#question'+qID+' input[type="text"]').autocomplete({
			minLength: 2, // This line is optional, signifies number of keystrokes before autocomplete is initiated
			source: ["Test1","Test2","Test3"]
		});
 
	});
 
</script>


2. Input from CSV file (a little trickier, requires several files. Suitable for larger lists and arrays).

Please note that, with this method, all processing happens browser-side. Thus, the entire contents of your CSV file will be exposed, no matter where you "hide" it (it is not possible to request files from outside the webroot on your server using javascript). If any of this concerns you, please proceed to the next paragraph, which deals with autocomplete based on database queries. To enable autocomplete based on output from a CSV file, you will need the following:

  1. A comma-separated data file (CSV)
  2. The jquery.csv.js plugin (v0.71)

First, download the jquery.csv.js script, and place it in the template folder (may require manual FTP, rather than upload through the template manager). You will need to call this script in the head section of the survey page, which you find in startpage.pstpl. Make sure you call it before {TEMPLATEJS}.

<script type="text/javascript" src="{TEMPLATEURL}jquery.csv.js"></script>


To make the CSV file, you may use Microsoft Excel and "Save as CSV". Note that Excel produces CSV files where the columns are separated by a semicolon, not a comma. To make this work with the jquery.csv.js plugin, you will have to edit the jquery.csv.js file on line 43:

41  $.csv = {
42    defaults: {
43      separator:',',  // Change this to: separator:';',
44      delimiter:'"',
45      headers:true
46    },


Once the CSV file is in place, add the following javascript to your question source, using the correct question ID (qID) and url/filename for your CSV file:

<script type="text/javascript" charset="utf-8">

        $(document).ready(function() {

                var qID = 1;

		var surveyRoot = location.pathname.split('index.php')[0];
                var url = surveyRoot+'datafile.csv';

                // Create an array to hold the data
                var testArr = new Array();

                // Grab the CSV contents
                $.get(url,function(data){
				
                        // Convert CSV contents to an array of arrays
                        fullArray = $.csv.toArrays(data);

                        // Load the data array
                        $(fullArray).each(function(i, item){
                                testArr.push(item[0]);
			});


                        // Initialise the autocomplete plugin
                        $('#question'+qID+' input.text').autocomplete({
                                source: testArr
			});
						
			// console.log(data); This would be a gould place to put your debugging script. Remember to comment out or remove before going into production, to economize on server workload
                });
        });

</script>


Now, the autocomplete should work. If not, see if your CSV file shows up under "Network" or "Resources" in your browser's inspector, and if it provides any output. Adding console.log(data); to your javascript (as indicated) should output the data to the browser console (you'll see the contents of your CSV file), and may be useful for debugging. Note that only the data from the current function (the function, within which the text was placed) will be presented.


3. Input from a MySQL database (trickiest, but also the most versatile and secure).
This method requires the following:

  1. Access to a MySQL database (tested with MySQL 5.5)
  2. A PHP script to connect to and query the database

First, paste the following script in your question source code, replacing for the correct question ID (qID) and the url/filename of your master PHP file (which in this case is autocomplete.php, see below):

<script type="text/javascript" charset="utf-8">

    $(document).ready(function() {
		
		var qID = 1;
		
		$('#question'+qID+' input[type="text"]').autocomplete({
			minLength: 2,  // This line is optional, signifies number of keystrokes before autocomplete is initiated
			source: 'autocomplete.php'
		});

	});
	
</script>


The structure of the database shouldn't matter too much, as long as you are using text fields. This example uses the UTF-8 character set. It is often a good idea to keep your login credentials in a separate file that you put outside of your webroot. In this case, I've put them in a file called constant.php:

<?php
// Login credentials
define('DB_SERVER', "localhost");
define('DB_USER', "username");
define('DB_PASSWORD', "password");
define('DB_DATABASE', "database_name");
define('DB_DRIVER', "mysql");
?>


The next file, database.php, deals with logging in to the database and performing the query that was requested through the javascript pasted above. Note that we're using the PDO convention rather than mysql_*, which has been deprecated for safety reasons (MySQLi remains a viable alternative, but is not shown here):

<?php

// Connect and query MySQL database, using PDO
try {
  $db = new PDO(DB_DRIVER . ":dbname=" . DB_DATABASE . ";host=" . DB_SERVER . ";charset=utf8", DB_USER, DB_PASSWORD);
}
 
catch(PDOException $e) {
    echo $e->getMessage();
}
 
$return_arr = array();
 
if ($db)
{
	$ac_term = "%".$_GET['term']."%";
	$query = "SELECT * FROM table_name WHERE column_name LIKE :term";
	$result = $db->prepare($query);
	$result->bindValue(":term",$ac_term); 
	$result->execute(); 
 
// Fetch data and store in array
	while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
		$row_array['label'] = $row['column_name'];
		$row_array['value'] = $row['column_name'];
 
        array_push($return_arr,$row_array);
    }
}


Finally, a PHP file (autocomplete.php) to include the previous two files and complete the query. This is the file that is called by the javascript, and the only one placed in the webroot:

<?php

// Include files outside of webroot
set_include_path('/var/www/surveys/test-survey');

// Fetch database credentials
require_once ('constant.php');

// Connect to database and perform query
require_once ('database.php');

// Clear query
$db = null;  
 
// Encode results in JSON. This is required for jquery autocomplete
echo json_encode($return_arr);

?>


This should complete your setup, but there are obviously a variety of pitfalls, at which point the previously outlined debugging techniques may be of help.

Use autocomplete to populate several fields

Tested with: 1.91+, IE 7/8/9, Firefox 18

This workaround uses the Autocomplete widget included with jQuery UI and the jquery.csv.js plugin to provide a respondent with a list of suggested answers as they type in a text input and then to automatically populate other fields depending on the selection.

For example, if you have 3 questions - "Name", "ID" and "Email" - you can use this workaround to autocomplete the "Name" question and then the "ID" and "Email" questions can be automatically populated with values associated with the selected name.

  • Download the jquery.csv.js plugin and place it in your template folder
  • If using LimeSurvey 1.9x, add the following line to your startpage.pstpl BEFORE the tag for template.js, if using 2.x, add it AFTER the {TEMPLATEJS} placeholder
    <script type="text/javascript" src="{TEMPLATEURL}jquery.csv.js"></script>
    
  • Create your CSV file with 3 columns (name, ID, email) as in this example and place it in your template folder
    Bob,    Bob's ID,    Bob's email
    
    Andy,    Andy's ID,    Andy's email
    
    Sam,    Sam's ID,    Sam's email
    
    Tony,    Tony's ID,    Tony's email
    
    Fred,    Fred's ID,    Fred's email
    
  • Create your 3 questions - "Name", "ID" and "Email". They must be in the same group/page.
  • Add the following styles to the end of your 'template.css' file
    /* Autocomplete styles */
    
    ul.ui-autocomplete {
    
       width: 250px !important;
    
       padding: 5px 10px;
    
       list-style: none;
    
    }
    
  • Add the following script to the source of one of the questions
  • Replace:
    • "NN" with the "Name" question ID
    • "II" with the "ID" question ID
    • "EE" with the "Email" question ID
  • Modify the path to your CSV file
    <script type="text/javascript" charset="utf-8">
    
    	$(document).ready(function() {
    
    		var qNameID = NN;
    
    		var qIDID = II;
    
    		var qEmailID = EE;
    
    		var url = "upload/templates/yourTemplate/yourCsvFile.csv";
    
    		// Create an array to hold the names
    		var namesArr = new Array();
    
    		// Grab the CSV contents
    		$.get(url,function(data){
    
    			// Convert CSV contents to an array of arrays
    			fullArray = $.csv.toArrays(data);
    
    			// Load the names array
    			$(fullArray).each(function(i, item){
    				namesArr.push(item[0]);
    			});
    
    			// Initialise the autocomplete plugin
    			$('#question'+qNameID+' input.text').autocomplete({
    				source: namesArr,
    				// Event fired when a selection is made (ui.item.value refers to the selected item)
    				select: function(event, ui) {
    					// Find the "ID" and "Email" values associated with the selected name value and load those questions
    					$(fullArray).each(function(i, item){
    						if(item[0] == ui.item.value) {
    							// The value from column 2 of the CSV
    							$('#question'+qIDID+' input.text').val(item[1]);
    							// The value from column 3 of the CSV
    							$('#question'+qEmailID+' input.text').val(item[2]);
    						}
    					});
    				}
    			});
    		});
    	});
    
    </script>
    

Create MaxDiff question type

See the Question design, layout and templating section for a workaround that uses JavaScript to convert an Array (flexible labels) by column question into a MaxDiff question type.

In a checkbox matrix, have the 'none' option in a row disable all other columns in that row

Tested with: LimeSurvey 1.90+

The questionId parameter is the base ID of the question matrix (multiflexible).

numColumns and numRows speak for themselves

specialColumns is an array of the columns that have the special ability that when one of them

is checked, none of the others can be checked. E.g., a 'none of these' and/or a 'do now know' option.

<script>

numColumns=13;

numRows=18;

questionId='28417X2X24';

specialColumns=[1,12,13];

function action(obj){

   locator=obj.id.replace('cbox_'+questionId, '').split('_');

   row=locator[0];

   column=locator[1];

   //alert('Action for row,column='+row+','+column);

   my_id='#cbox_'+questionId+row+'_'+column+''

   if($(my_id).attr('checked') == false) {

       for( var i=1; i<=numColumns; i++ ) {

           if(i==column) continue;

           id='#cbox_'+questionId+row+'_'+i

           $(id).attr('disabled',false);

       }

   } else {

       for( var i=1; i<=numColumns; i++ ) {

           if(i==column) continue;

           id='#cbox_'+questionId+row+'_'+i

           aid='#answer'+questionId+row+'_'+i

           $(aid).val(0);

           $(id).attr('checked',false);

           $(id).attr('disabled',true);

       }

   }

}

function customOnLoad(){

   for( var row=1; row<=numRows; row++ ) {

       for( var k=0; k<specialColumns.length; k++ )

       {

           column=specialColumns[k];

           $('#cbox_'+questionId+row+'_'+column).change(function() {

               action(this);

           });

       }

   }

}

jQuery(document).ready(

   function(){

   customOnLoad();

   }

);

</script>

Last Option In Array (Numbers) (Checkboxes) Row Excludes All Others

Tested with LimeSurvey Versions 2.06 and 2.54

This workaround uses JavaScript/jQuery to make the last option of each Array (Numbers) (Checkboxes) row exclude all other options in the same row.

The script will uncheck all options in a row if the last option is checked and uncheck the last option in a row if any other options are checked.

Implementation LS Version 2.06:

  1. Set up your survey to use JavaScript.
  2. Place the following script in the source of the array question.
    <script type="text/javascript" charset="utf-8">
    	$(document).ready(function() {
    		// Call the exclude function using question ID
    		excludeOpt({QID});
    	});
       
    	// A function to make the last option in each array row exclusive
    	function excludeOpt (qID) {
    	   
    		var thisQuestion = $('#question'+qID)
    
    		// Add some classes to the checkbox cells
    		$('td.checkbox-item', thisQuestion).addClass('normalOpt');
    		$('tr.subquestions-list', thisQuestion).each(function(i) {
    			$('.normalOpt:last', this).removeClass('normalOpt').addClass('exlusiveOpt')
    		});
    
    		// A listener on the checkbox cells
    		$('td.checkbox-item', thisQuestion).click(function (event) {
    			handleExclusive($(this));
    		});
    
    		// A listener on the checkboxes
    		$('td.checkbox-item input[type="checkbox"]', thisQuestion).click(function (event) {
    			handleExclusive($(this).closest('td'));
    		});
    		
    		function handleExclusive(thisCell) {
    			   
    			var thisRow = $(thisCell).closest('tr');
    
    			// Uncheck the appropriate boxes in a row
    			if ($(thisCell).hasClass('normalOpt')) {
    				$('.exlusiveOpt input[type="checkbox"]', thisRow).attr('checked', false);
    			}
    			else {
    				$('.normalOpt input[type="checkbox"]', thisRow).attr('checked', false);
    			}
    				
    			// Check conditions (relevance)
    			$('td.checkbox-item', thisRow).each(function(i) {
    				var thisValue = '';
    				if($('input[type="checkbox"]', this).is(':checked')) {
    					thisValue = 1;
    				}
    				var thisSGQA = $('input[type="checkbox"]', this).attr('id').replace(/cbox_/, '');
    				 
    				$('input[type="hidden"]', this).attr('value', thisValue);
    				fixnum_checkconditions(thisValue, thisSGQA, 'hidden');
    			});
    		}
    	}
    </script>
    

Implementation LS Version 3.x:

  1. Set up your survey to use JavaScript.
  2. Place the following script in the source of the array question.
    <script type="text/javascript" charset="utf-8">
    	$(document).on('ready pjax:scriptcomplete',function(){
    	   
    		var thisQuestion = $('#question{QID}')
    
    		// Add some classes to the checkbox cells
    		$('td.checkbox-item:last-child', thisQuestion).addClass('exclusive-item');
    		
    		// A function to un-check boxes
    		function resetCheckbox(thisItem) {
    			$(':hidden', thisItem).val('');
    			$(':checkbox', thisItem).prop('checked', false).trigger('change');
    		}
    		
    		// A listener on the checkboxes
    		$(':checkbox', thisQuestion).on('change', function(e) {
    			if($(this).is(':checked')) {
    				var thisItem = $(this).closest('.answer-item');				
    				var thisRow = $(this).closest('tr');
    				var items = $('td.answer-item.exclusive-item', thisRow);
    				if($(thisItem).hasClass('exclusive-item')) {
    					items = $('td.answer-item:not(.exclusive-item)', thisRow);
    				}
    				$.each(items, function(i, el) {
    					resetCheckbox(el);
    				});
    			}
    		});
    	});
    </script>
    

Add Navigation Buttons (Next and Previous) To Top Of Page

Tested with: 1.90+, IE 7/8, Firefox 3.6, Safari 3.2

Add the following to the end of template.js. It will insert a new div after the progress bar and then insert clones of the nav buttons in the new div.

This example is for the default template in 1.90 or 1.91. The placement of the new div may need to be modified for other templates.

   $(document).ready(function() {

		// Insert a new div after the progress bar
		$('<div id="navigator2" />').insertAfter('#progress-wrapper');

		// Style the new div
		$('#navigator2').css({
			'text-align':'center'
		});

		// Insert clones of the nav buttons in the new div
		$('input.submit:eq(0)').clone().appendTo('#navigator2');
		$('input.submit:eq(2)').clone().appendTo('#navigator2');

		// Give the new buttons some new IDs
		$('#navigator2 input.submit').each(function(i) {
			var oldID = $(this).attr('id');
			var newID = oldID + '2';
			$(this).attr('id', newID)
		});
	});

How to add an opt out link to your survey

Tested with: 1.91+, Firefox 5.0

This javascript inserts a link to opt out of the survey above the progress bar.

How to use script here

Then insert the following code into to the source of a group description or a question. I used the group description.

 <script type="text/javascript" charset="utf-8">

   $(document).ready(function() {

		// Find the survey ID
		if($('input#fieldnames').length != 0) {
			var fieldNames = $('input#fieldnames').attr('value');
			var tmp = fieldNames.split('X');
			var sID = tmp[0];
		}

		// Insert a new div after the progress bar
		$('<div id="optOutLink" />').insertBefore('#progress-wrapper');

		// Style the new div
		$('#optOutLink').css({
			'text-align':'center'
		});

		// Insert opt out url in the new div
		$('#optOutLink').html('<p><a href="optout.php?lang=en&sid;='+sID+'&token;={TOKEN}">Click here to opt out of this survey.</a></p><br>');

	});

</script>

Anonymously track respondents answers across multiple surveys

Tested with: 1.91+ and 1.92+

This workaround uses JavaScript/jQuery to enable tracking of respondents across multiple surveys ensuring anonymity (it does not use tokens).

To do this you have to use this question in all the surveys you want to link respondents answers.

This workaround create an ID by asking some partial personal information (like initials, birth month ...). The ID will be encrypted using a one-way function (in this case SHA-1) and stored in a short text answer.

Implementation is as follows:

  1. Set up your survey to use JavaScript.
  2. Place the following code in the source of a short text question.
  3. Optional: you can also add other select field to avoid collisions (e.g. for large population sample)
<p>This answers are used to generate your ID. These information will be encrypted, it will not be possible in any case to identify you neither from your answers nor from your ID.</p>

<table>

<tr>

<td>Initial of your name</td>

<td>

<select id="field1">

   <option value=""></option>

   <option value="A">A</option>

   <option value="B">B</option>

   <option value="C">C</option>

   <option value="D">D</option>

   <option value="E">E</option>

   <option value="F">F</option>

   <option value="G">G</option>

   <option value="H">H</option>

   <option value="I">I</option>

   <option value="J">J</option>

   <option value="K">K</option>

   <option value="L">L</option>

   <option value="M">M</option>

   <option value="N">N</option>

   <option value="O">O</option>

   <option value="P">P</option>

   <option value="Q">Q</option>

   <option value="R">R</option>

   <option value="S">S</option>

   <option value="T">T</option>

   <option value="U">U</option>

   <option value="V">V</option>

   <option value="W">W</option>

   <option value="X">X</option>

   <option value="Y">Y</option>

   <option value="Z">Z</option>

</select>

</td>

</tr>

<tr>

<td>Your birth month</td>

<td>

<select id="field2">

   <option value=""></option>

   <option value="01">January</option>

   <option value="02">February</option>

   <option value="03">March</option>

   <option value="04">April</option>

   <option value="05">May</option>

   <option value="06">June</option>

   <option value="07">July</option>

   <option value="08">August</option>

   <option value="09">September</option>

   <option value="10">October</option>

   <option value="11">November</option>

   <option value="12">December</option>

</select>

<td>

</tr>

</table>

<script type="text/javascript" src="http://crypto-js.googlecode.com/svn/tags/3.0.2/build/rollups/sha1.js"></script>

<script type="text/javascript">

$(document).ready(function() {

   $("#answer{SGQ}").attr('readonly', 'readonly');

   //$("#answer{SGQ}").css('display', 'none'); //uncomment this line for not displaying the code

});

$('#field1, #field2').bind('change keyup', function() {

   if ($("#field1").val()=='' || $("#field2").val()=='')

       $("#answer{SGQ}").val('');

   else {

       var sha1 = CryptoJS.algo.SHA1.create();

       sha1.update($("#field1").val());

       sha1.update($("#field2").val());

       // sha1.update($("#otherfield").val());

       var hash = sha1.finalize();

       $("#answer{SGQ}").val(hash);

   }

});

</script>

Check validity of international phone number in subquestion

Tested with: 1.91+, Firefox 8, Chrome 15

As of version 1.92, you can use the core Validation querstion option to validate multiple short text and array text questions. Any question that fails that validation check will have its background color changed to red.

Note, the below workaround will fail in version 1.92 because it contains a regular expression with curly braces. However, as described above, support for this feature is now built-in to version 1.92

This workaround uses javascript to check the validity of an international phone number in a subquestion of multiple short text.

Add the following code to the source of your question.

Replace "QQ" with the question ID and "NN" with the row number that you want to validate.

<script type="text/javascript" charset="utf-8">

   $(document).ready(function() {

       // Call the phone function with the question ID and the row number

       phoneTest(QQ, NN);

       function phoneTest(qID, inputNum) {

           // Interrupt next/submit function

           $('#movenextbtn, #movesubmitbtn').click(function(){

               // Some vars - modify as required

               var phoneMatch = /^[0-9,+,(), ,]{1,}(,[0-9]+){0,}$/;

               var msg1 = 'Please enter a valid phone number.';

               // Test the input

               var phoneInput = $('#question'+qID+' li:eq('+(inputNum-1)+') input.text').val();

               if(phoneInput != '' && !phoneMatch.test(phoneInput)) {

                   alert(msg1);

                   $('#question'+qID+' li:eq('+(inputNum-1)+') input.text').css({ 'background':'pink' });

                   return false;

               }

               else {

                   $('#question'+qID+' li:eq('+(inputNum-1)+') input.text').css({ 'background':'' });

                   return true;

               }

           });

       }

   });

</script>

The regex is from: regexlib.com blank

Check validity of email address in subquestion

'Tested with: 1.91+, Firefox 8, Chrome 15

As of version 1.92, you can use the core Validation querstion option to validate multiple short text and array text questions. Any question that fails that validation check will have its background color changed to red.
Note, the below workaround will fail in version 1.92 because it contains a regular expression with curly braces. However, as described above, support for this feature is now built-in to version 1.92

This workaround uses javascript to check the validity of an email address in a subquestion of multiple short text.

Add the following code to the source of your question.

Replace "QQ" with the question ID and "NN" with the row number that you want to validate.

<script type="text/javascript" charset="utf-8">

   $(document).ready(function() {

       // Call the email function with the question ID and the row number

       emailTest(QQ, NN);

       function emailTest(qID, inputNum) {

           // Interrupt next/submit function

           $('#movenextbtn, #movesubmitbtn').click(function(){

               // Some vars - modify as required

               var emailMatch = /^[a-zA-Z0-9\_\-]+[a-zA-Z0-9\.\_\-]*@([a-zA-Z0-9\_\-]+\.)+([a-zA-Z]{2,4}|travel|museum)$/;

               var msg1 = 'Please enter a valid email address.';

               // Test the input

               var emailInput = $('#question'+qID+' li:eq('+(inputNum-1)+') input.text').val();

               if(emailInput != '' && !emailMatch.test(emailInput)) {

                   alert(msg1);

                   $('#question'+qID+' li:eq('+(inputNum-1)+') input.text').css({ 'background':'pink' });

                   return false;

               }

               else {

                   $('#question'+qID+' li:eq('+(inputNum-1)+') input.text').css({ 'background':'' });

                   return true;

               }

           });

       }

   });

</script>

The regex is from: regexlib.com blank

Hiding the help-text when it's empty

Tested with: 1.91+

I had made a style in my template putting a border around my help-text. To avoid empty help-text to show up (and have lines instead of just 'nothing' when there was no help text, I used this piece of code in question.pstpl:

<script type="text/javascript">

                   var questionhelp="{QUESTIONHELP}";

                   if (questionhelp=="") {

                          document.write("");}

                   else {

                   document.write('<tr><td class="survey-question-help">'+ questionhelp +'</td></tr>');}

               </script>

it can probably be optimized but it worked for me

Disable or hide selected fields in a matrix question

Tested with: 1.92+, IE 7/8/9, FireFox 12/13

TASK

Hide or disable selected fields in a Matrix, so that users cannot make any input.

(See example, where users should not make input into red framed fields)

Fig. 1: Prevent users from input into first two fields (as an example)

SOLUTION

First find the name of the fields you want to hide / disable. In my case I used Firebug with firefox to figure that out.

Fig. 2: Figure out the name(s) of the field(s)

Then put the following script-code into the HTML-source code as described above:

Note: you have to change the code to match the names of your fields!

<script type="text/javascript" charset="utf-8">

   $(document).ready(function() {

       $('input[name="69965X2X10SQ001_SQ001"]').attr('disabled', 'disabled');

       $('input[name="69965X2X10SQ001_SQ002"]').attr('hidden', 'hidden');

   });

</script>

Fig. 3: Result: first field is diabled ("greyed out"), second one is completely hidden

I think this way one can apply many other attributes to a field like color etc. But did not try that myself.

Thanks to roB2009 for the input in the forum!

Add prefix or suffix to question type "Array (Texts)"

TASK

Add a prefix or suffix to the text fields in questions of the "Array (Texts)" type.

Fig. 1: Suffix

Fig. 1: Prefix

SOLUTION

  • Set up your survey to use JavaScript.
  • Add the following script to the source of the array. Replace "12345" with the array question ID and modify the suffixes/prefixes as required.
  • The function call must consist of a question ID followed by the suffixes for each column. Suffixes/Prefixes must be enclosed in quotes and comma separated.
  • In this example question ID 12345 will get an "in €" suffix in the first column and an "in %" in the next 3 columns.
<script type="text/javascript" charset="utf-8">

   $(document).ready(function() {

       // Call the function with the question ID followed by a comma-separated list of suffixes

       addSuffix(12345, 'in &euro;', 'in %', 'in %', 'in %');

       function addSuffix() {

           var qID = $(arguments)[0];

           // Assign some column-specific classes

           $('#question'+qID+' table.question tbody tr').each(function(i){

               $('td', this).each(function(i){

                   $(this).addClass('answerCol-'+(i+1)+'');

               });

           });

           // Some styling

           $('#question'+qID+' table.question tbody input[type="text"]').css({

               'width': '50%'

           });

           // Insert the suffixes

           $(arguments).each(function(i, val){

               if(i > 0) {

                   $('#question'+qID+' td.answerCol-'+i+' label').append(val);

               }

           });

       }

   });

</script>

For adding a prefix you need to substitute the following line in the code

$('#question'+qID+' td.answerCol-'+i+' label').append(val);

with this

$('#question'+qID+' td.answerCol-'+i+' label').prepend(val);

Time question (Date question-like)

Tested with: 1.92+

This workaround uses JavaScript/jQuery to build a Time question just like to the Date one.

Implementation is as follows:

  1. Download the file at trentrichardson.com/examples/timepicker/jquery-ui-timepicker-addon.js and upload it to your survey.
  2. Set up your survey to use JavaScript.
  3. Place the following code in the source of a short text question.
<script src="/upload/surveys/{SID}/jquery-ui-timepicker-addon.js"></script>

<script>

	$(document).ready(function(){

		$('#answer{SGQ}').timepicker({
			timeText: 'Time', //here translation
			hourText: 'hour', //here translation
			minuteText: 'minute', //here translation
			closeText: 'Done' //here translation
		});

		$('#answer{SGQ}').attr('readonly', 'readonly');

		$( "<style>"
		+".ui-widget-header, .ui-datepicker-current { display:none; }"
		+".ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; }"
		+".ui-timepicker-div dl dd { margin: 0 10px 10px 65px; }"
		+" }"
		+"</style>").appendTo( "head" );

	});

</script>

Calculating date difference

Tested with: 1.92+ Build 120909

This workaround explains how calculate difference between two dates (in: days, weeks, months and years) and is based on this resource external.

Requieres 2 groups and 'group by group' or 'question by question' presentation.

Group 1 (2 date questions)

  • 1st question - Question code: "DD"
  • 2nd question - Question code: "D2"

Group 2

  • 1 numerical input question.

This question retrieves the number of days between the 2 dates, and check if D2 is posterior than DD.

All the requiered code have to be added in the source of this numerical question.

<script type="text/javascript">

/*

Adapted from http://ditio.net/2010/05/02/javascript-date-difference-calculation/
*/

var DateDiff = {

   inDays: function(d1, d2) {

       var t2 = d2.getTime();

       var t1 = d1.getTime();

       return parseInt[[t2-t1)/(24*3600*1000]];

   },

   inWeeks: function(d1, d2) {

       var t2 = d2.getTime();

       var t1 = d1.getTime();

       return parseInt[[t2-t1)/(24*3600*1000*7]];

   },

   inMonths: function(d1, d2) {

       var d1Y = d1.getFullYear();

       var d2Y = d2.getFullYear();

       var d1M = d1.getMonth();

       var d2M = d2.getMonth();

       return (d2M+12*d2Y)-(d1M+12*d1Y);

   },

   inYears: function(d1, d2) {

       return d2.getFullYear()-d1.getFullYear();

   }

}

d1 = new Date({substr(DD,0,4)}, {substr(DD,5,2)}, {substr(DD,8,2)});

d2 = new Date({substr(D2,0,4)}, {substr(D2,5,2)}, {substr(D2,8,2)});

// Displays result

document.write("<br />Number of <b>days</b>: "+DateDiff.inDays(d1, d2));

document.write("<br />Number of <b>weeks</b>: "+DateDiff.inWeeks(d1, d2));

document.write("<br />Number of <b>months</b>: "+DateDiff.inMonths(d1, d2));

document.write("<br />Number of <b>years</b>: "+DateDiff.inYears(d1, d2));

// filled the numerical input with number of days (or nights)

var nDays = +DateDiff.inDays(d1, d2);

    jQuery(document).ready(function() {

       $(".numeric:eq(0) input.text").val(nDays);

               $(".numeric:eq(0) input.text").attr('readonly','readonly');

// check if d2 > d1

               $('#movenextbtn').hide();

               $('#movesubmitbtn').hide();

               if(nDays >= 0){

       // automatic submit (uncomment if needed)

               // document.limesurvey.submit();

               // $('body').hide();

                 }

               else alert("Check out date must be posterior than Check in\nPlease return to the previous page and edit your answers");

   });

</script>

How to set minDate/maxDate based on other Datequestions

tested with Version 2.00+ Build 130708

QQ1 and QQ2 are the question identifiers. QQ2 gets as maxDate today and as minDate the date set in QQ1. The onClose event is needed because on document.ready the value of QQ1 is not yet known, although the value of today is.

<script type="text/javascript" charset="utf-8">
    $(document).ready(function () {
        'use strict';
        var qID1 = QQ1,
            qID2 = QQ2,
            q1 = $('#question' + 'QQ1' + ' .popupdate'), 
            q2 = $('#question' + 'QQ2' + ' .popupdate');
	
        //if q1 loses focus, its value is taken and used as minDate in q2   
        q1.datepicker("option", "onClose", function (datum){
            q2.datepicker( "option", "minDate", datum );
        });
        //q2 gets today as its maxDate
        q2.datepicker( 'option', 'maxDate', new Date() );
});
</script>

Remove Question Help if empty

Version 2.00+ Build 130513

Change your question.pstpl in your Template Editor so that the line looks like below. I've added the ID tag help_{question_number}.

<tr><td class="survey-question-help" id=help_{QUESTION_NUMBER}>{QUESTIONHELP}</td></tr>

You can then add the following javascript at the end of question.pstpl. If the contents of the help tag is empty then hide the element.

<script>
$(document).ready(function(){
  if($('#help_{QUESTION_NUMBER}').is(':empty')){
  $('#help_{QUESTION_NUMBER}').hide();
  }
});
</script>

Implicit Association Test (IAT)

Version 2.06

Tested with: LimeSurvey version 2.06

This workaround uses JavaScript to insert an IAT interface into an Array (Texts) question. The workaround prompts respondents to assign words or phrases using the "E" or "I" keys. In mobile devices, "E" and "I" buttons are provided.



Download a working template and working survey.

Implementation is as follows:

1) Set up your survey to use JavaScript.

2) Create your Array (Texts) with subquestions as follows:
- Y scale - all of your "attribut" words or phrases
- X scale - "Key Pressed" and "Elapsed Time (ms)"

3) Insert your association terms and the other text elements for the interface into the Question Help section as depicted below. The text strings must be separated with "double pipes" (||) and you will need to place them in the following order:
- Left association term
- Right association term
- Instructional text
- Finished text

4) Add the following to the end of template.js:

// A plugin to insert an IAT interface
function implicitAssociation(el, showAnswers) {
		
	$(el).each(function() {
			
		var thisQuestion = $(this);
		var thisQuestionHelp = $('img[src$="help.gif"]', thisQuestion).parent();
		var thisQuestionAnswers = $('table.question', thisQuestion).parent();
		var startTime = '';
		var endTime = '';	
		var agent = navigator.userAgent.toLowerCase();
		var mobile = false;
		if( /android|webos|iphone|ipad|ipod|blackberry/i.test(navigator.userAgent) ) { // Mobile devices
			mobile = true;
		}
		
		// Some classes
		$(thisQuestion).addClass('iatQuestion');
		$('.subquestion-list').addClass('unanswered');
		
		// Hide the question help element
		$(thisQuestionHelp).hide();
		
		// Hide the answers
		if(showAnswers != true) {
			$('table.question', thisQuestion).hide();
		}
		
		// Insert IAT display
		var iatTexts = $(thisQuestionHelp).text().split('||');
		var iatDisplayHTML = '<div class="iatWrapper">\
								<div class="iatLeftLabel">'+iatTexts[0]+'</div>\
								<div class="iatRightLabel">'+iatTexts[1]+'</div>\
								<div class="iatWord"></div>\
								<div class="iatInstructions">'+iatTexts[2]+'</div>\
							</div>\
							<div class="iatMobileButtonWrapper">\
								<div class="iatButton iatLeftButton">E</div>\
								<div class="iatButton iatRightButton">I</div>\
								<div style="width:100%; clear:both;"></div>\
							</div>';
		$(thisQuestionAnswers).prepend(iatDisplayHTML);
		
		// Show a word
		function iatShowWord() {
			$('div.iatWord', thisQuestion).text($('.subquestion-list.unanswered:first .answertext', thisQuestion).text());
			startTime = new Date();
			
			$(document).bind('keypress.iatKeypress', function(e) {
				if(e.which == 101 || e.which == 105) {
					var thisRow = $('.subquestion-list.unanswered:eq(0)', thisQuestion);
					$(thisRow).removeClass('unanswered');
					endTime = new Date();
					$('input[type="text"]:eq(1)', thisRow).val(endTime.valueOf() - startTime.valueOf());
					if(e.which == 101) {
						$('input[type="text"]:eq(0)', thisRow).val('E');
					}
					else {
						$('input[type="text"]:eq(0)', thisRow).val('I');
					}
					$(document).unbind('keypress.iatKeypress');
					if($('.subquestion-list.unanswered', thisQuestion).length > 0) {
						iatShowWord();
					}
					else {
						$('.iatLeftLabel, .iatWord, .iatRightLabel, .iatInstructions', thisQuestion).fadeOut('slow', function() {
							$('div.iatWord', thisQuestion).text(iatTexts[3]);
							$('.iatWord', thisQuestion).addClass('done').fadeIn('slow');
						});
					}
				}
			});
		}
		function iatShowWordMobile() {
			$('div.iatWord', thisQuestion).text($('.subquestion-list.unanswered:first .answertext', thisQuestion).text());
			startTime = new Date();
			
			$('.iatButton', thisQuestion).bind('click.iatButtonClick', function(e) {
				var thisRow = $('.subquestion-list.unanswered:eq(0)', thisQuestion);
				$(thisRow).removeClass('unanswered');
				endTime = new Date();
				$('input[type="text"]:eq(1)', thisRow).val(endTime.valueOf() - startTime.valueOf());
				$('input[type="text"]:eq(0)', thisRow).val($(this).text());
				$('.iatButton', thisQuestion).unbind('click.iatButtonClick');
				if($('.subquestion-list.unanswered', thisQuestion).length > 0) {
					iatShowWordMobile();
				}
				else {
					$('.iatLeftLabel, .iatWord, .iatRightLabel, .iatInstructions, .iatMobileButtonWrapper', thisQuestion).fadeOut('slow', function() {
						$('div.iatWord', thisQuestion).text(iatTexts[3]);
						$('.iatWord', thisQuestion).addClass('done').fadeIn('slow');
					});
				}
			});
		}
		
		// Start the IAT display
		if(mobile == true) { // Mobile devices
			$('.iatMobileButtonWrapper', thisQuestion).show();
			$('.iatButton', thisQuestion).bind('click.iatStartMobile', function(e) {
				$('.iatButton', thisQuestion).unbind('click.iatStartMobile');
				iatShowWordMobile();
			});
		}
		else {
			$(document).bind('keypress.iatStart', function(e) { // Non-mobile devices
				if(e.which == 101 || e.which == 105) {
					$(document).unbind('keypress.iatStart');
					iatShowWord();
				}
			});
		}
	});
}


5) Add the following to the end of template.css:

/* IAT questions */

.iatWrapper {
	position: relative;
	width: 600px;
	height: 300px;
	color: #FFFFFF;
	background-color: #000000;
	font-size: 21px;
}

.iatLeftLabel {
	float: left;
	padding: 10px;
}

.iatRightLabel {
	float: right;
	padding: 10px;
}

.iatWord {
	position: absolute;
	width: 100%;
	top: 35%;
	text-align: center;
	font-size: 36px;
	color:#0F0;
}

.iatWord.done {
	color:#FFFFFF;
}

.iatInstructions {
	position: absolute;
	width: 100%;
	bottom: 0px;
	padding-bottom: 10px;
	text-align: center;
}

.iatMobileButtonWrapper {
	display: none;
	position: relative;
	width: 600px;
	font-size: 36px;
}

.iatButton {
	float: left;
	margin: 20px;
	width: 100px;
	padding: 2px 0 5px 0;
	border: 5px solid #999;
	-moz-border-radius: 10px;
	-webkit-border-radius: 10px;
	-khtml-border-radius: 10px;
	border-radius: 10px;
	text-align: center;
	line-height: normal;
	cursor: pointer;
}

.iatLeftButton {
	float: left;
}

.iatRightButton {
	float: right;
}


6) To apply the plugin, add one of the two scripts below to the question source:
- With hidden answer table (for normally activated survey)

<script type="text/javascript" charset="utf-8">	
	$(document).ready(function() {
		// Apply the IAT plugin to this question
		implicitAssociation('#question{QID}');
	});	
</script>

- With visible answer table (for testing purposes)

<script type="text/javascript" charset="utf-8">	
	$(document).ready(function() {
		// Apply the IAT plugin to this question
		implicitAssociation('#question{QID}', true);
	});	
</script>

Version 2.50

Tested with: LimeSurvey version 2.50

This workaround uses JavaScript to insert an IAT interface into an Array (Texts) question. The workaround prompts respondents to assign words or phrases using the "E" or "I" keys. In mobile devices, "E" and "I" buttons are provided.



DOWNLOADS:

IMPLEMENTATION:

  1. Set up your survey to use JavaScript.
  2. Create your Array (Texts) with subquestions as follows:
    • Y scale - all of your "attribute" words or phrases
    • X scale - "Key Pressed" and "Elapsed Time (ms)"
  3. Insert your association terms and the other text elements for the interface into the Question Help section as depicted below. The text strings must be separated with "double pipes" (||) and you will need to place them in the following order:
    • Left association term
    • Right association term
    • Instructional text
    • Finished text
  4. Add the following to the end of template.js:
    // A plugin to insert an IAT interface
    function implicitAssociation(el, showAnswers) {
    	
    	$(el).each(function() {
    			
    		var thisQuestion = $(this);
    		var thisQuestionHelp = $('div.question-help', thisQuestion);
    		var thisQuestionAnswers = $('table.subquestion-list', thisQuestion).parent();
    		var startTime = '';
    		var endTime = '';	
    		var agent = navigator.userAgent.toLowerCase();
    		var mobile = false;
    		if( /android|webos|iphone|ipad|ipod|blackberry/i.test(navigator.userAgent) ) { // Mobile devices
    			mobile = true;
    		}
    		
    		// Some classes
    		$(thisQuestion).addClass('iat-question');
    		$('tr.subquestion-list', thisQuestion).addClass('unanswered');
    		
    		// Hide the question help element
    		$(thisQuestionHelp).hide();
    		$(thisQuestionHelp).closest('.question-help-container').hide();
    		
    		// Hide the answers
    		if(showAnswers != true) {
    			$('table.subquestion-list', thisQuestion).hide();
    		}
    		
    		// Insert IAT display
    		var iatTexts = $(thisQuestionHelp).text().split('||');
    		var iatDisplayHTML = '<div class="iatWrapper">\
    								<div class="iatLeftLabel">'+iatTexts[0]+'</div>\
    								<div class="iatRightLabel">'+iatTexts[1]+'</div>\
    								<div class="iatWord"></div>\
    								<div class="iatInstructions">'+iatTexts[2]+'</div>\
    							</div>\
    							<div class="iatMobileButtonWrapper">\
    								<div class="iatButton iatLeftButton">E</div>\
    								<div class="iatButton iatRightButton">I</div>\
    								<div style="width:100%; clear:both;"></div>\
    							</div>';
    		$(thisQuestionAnswers).prepend(iatDisplayHTML);
    		
    		// Show a word
    		function iatShowWord() {
    			$('div.iatWord', thisQuestion).text($('tr.subquestion-list.unanswered:first .answertext', thisQuestion).text());
    			startTime = new Date();
    			
    			$(document).bind('keypress.iatKeypress', function(e) {
    				if(e.which == 101 || e.which == 105) {
    					var thisRow = $('tr.subquestion-list.unanswered:eq(0)', thisQuestion);
    					$(thisRow).removeClass('unanswered');
    					endTime = new Date();
    					$('input[type="text"]:eq(1)', thisRow).val(endTime.valueOf() - startTime.valueOf());
    					if(e.which == 101) {
    						$('input[type="text"]:eq(0)', thisRow).val('E');
    					}
    					else {
    						$('input[type="text"]:eq(0)', thisRow).val('I');
    					}
    					$(document).unbind('keypress.iatKeypress');
    					if($('tr.subquestion-list.unanswered', thisQuestion).length > 0) {
    						iatShowWord();
    					}
    					else {
    						$('.iatLeftLabel, .iatWord, .iatRightLabel, .iatInstructions', thisQuestion).fadeOut('slow', function() {
    							$('div.iatWord', thisQuestion).text(iatTexts[3]);
    							$('.iatWord', thisQuestion).addClass('done').fadeIn('slow');
    						});
    					}
    				}
    			});
    		}
    		function iatShowWordMobile() {
    			$('div.iatWord', thisQuestion).text($('tr.subquestion-list.unanswered:first .answertext', thisQuestion).text());
    			startTime = new Date();
    			
    			$('.iatButton', thisQuestion).bind('click.iatButtonClick', function(e) {
    				var thisRow = $('tr.subquestion-list.unanswered:eq(0)', thisQuestion);
    				$(thisRow).removeClass('unanswered');
    				endTime = new Date();
    				$('input[type="text"]:eq(1)', thisRow).val(endTime.valueOf() - startTime.valueOf());
    				$('input[type="text"]:eq(0)', thisRow).val($(this).text());
    				$('.iatButton', thisQuestion).unbind('click.iatButtonClick');
    				if($('tr.subquestion-list.unanswered', thisQuestion).length > 0) {
    					iatShowWordMobile();
    				}
    				else {
    					$('.iatLeftLabel, .iatWord, .iatRightLabel, .iatInstructions, .iatMobileButtonWrapper', thisQuestion).fadeOut('slow', function() {
    						$('div.iatWord', thisQuestion).text(iatTexts[3]);
    						$('.iatWord', thisQuestion).addClass('done').fadeIn('slow');
    					});
    				}
    			});
    		}
    		
    		// Start the IAT display
    		if(mobile == true) { // Mobile devices
    			$('.iatMobileButtonWrapper', thisQuestion).show();
    			$('.iatButton', thisQuestion).bind('click.iatStartMobile', function(e) {
    				$('.iatButton', thisQuestion).unbind('click.iatStartMobile');
    				iatShowWordMobile();
    			});
    		}
    		else {
    			$(document).bind('keypress.iatStart', function(e) { // Non-mobile devices
    				if(e.which == 101 || e.which == 105) {
    					$(document).unbind('keypress.iatStart');
    					iatShowWord();
    				}
    			});
    		}
    	});
    }
  5. Add the following to the end of template.css:
    /* IAT questions */
    
    .iatWrapper {
    	position: relative;
    	width: 100%;
    	max-width: 600px;
    	height: 300px;
    	margin: 0 auto;
    	color: #D0DBE5;
    	background-color: #2C3E50;
    	font-size: 21px;
    }
    
    .iatLeftLabel {
    	float: left;
    	padding: 10px;
    }
    
    .iatRightLabel {
    	float: right;
    	padding: 10px;
    }
    
    .iatWord {
    	position: absolute;
    	width: 100%;
    	top: 35%;
    	text-align: center;
    	font-size: 36px;
    	color: #C4D1DE;
    }
    
    .iatWord.done {
    	color: #C4D1DE;
    }
    
    .iatInstructions {
    	position: absolute;
    	width: 100%;
    	bottom: 0px;
    	padding-bottom: 10px;
    	text-align: center;
    }
    
    .iatMobileButtonWrapper {
    	display: none;
    	position: relative;
    	width: 100%;
    	max-width: 600px;
    	margin: 0 auto;
    	font-size: 36px;
    }
    
    .iatButton {
    	float: left;
    	margin: 20px;
    	width: 100px;
    	padding: 2px 0 5px 0;
    	border: 5px solid #233140;
    	-moz-border-radius: 10px;
    	-webkit-border-radius: 10px;
    	-khtml-border-radius: 10px;
    	border-radius: 10px;
    	text-align: center;
    	line-height: normal;
    	cursor: pointer;
    }
    
    .iatLeftButton {
    	float: left;
    }
    
    .iatRightButton {
    	float: right;
    }
  6. To apply the plugin, add one of the two scripts below to the question source:
    • With hidden answer table (for normally activated survey)
      <script type="text/javascript" charset="utf-8">	
      	$(document).ready(function() {
      		// Apply the IAT plugin to this question
      		implicitAssociation('#question{QID}');
      	});	
      </script>
    • With visible answer table (for testing purposes)
      <script type="text/javascript" charset="utf-8">	
      	$(document).ready(function() {
      		// Apply the IAT plugin to this question
      		implicitAssociation('#question{QID}', true);
      	});	
      </script>

Version 3.x

Here is a custom question theme for an Implicit Association Test (IAT) in version 3.x - https://github.com/tpartner/LimeSurvey-Implicit-Association-Test

Make multiple choice easier

Tested with Version 2.00 in Chrome, FF, Safar, IE

The problem: We often have multiple choice question, where the user has to select 4 elements out of 78 possible answers or 3 out of 25 and so on. If we use pen and paper version for this questions, we noticed that the participants go through the whole list and if an answer doesn't suit on the first sight, they cross it out.

The solution: The script gives the participant the possibility to cross out this answers in a digital way. The script adds a small "X" before the checkbox. When it is clicked, the label text of the checkbox turns grey. (see the attached screenshot for an example)

Here is the script, just add it to help text

<script type="text/javascript">
 
/**
* Small script that adds a "X" before a checkbox. 
* On click the text color of the label of the checkbox turns grey
* If the user has to choose some answers from many options, he can "delete" answers, that on first sight looks wrong
* Add the script to the question in Limesurvey (for example within the source code of the help text)
*/
 
//Define the X (standard is a X in capitals)
var x = "X";
css_class = "x";
 
/*
* Define the style of the X
* Of course you can define the style in the stylesheet - if so, the script ignores the following settings
* If there is a style, than set style var to 1
*/
var style = 0;
var size = "12px";
var weight = "bold";
var color = "red";
var cursor = "pointer";
 
//element type - standard is a span element
var type = "span";
 
//Define the new font color of the checkbox label
label_color = "#bfbfbf";
 
 
//End of configuration - start of the function
 
//Define the element that is added
if(style == 1) {
	ele = "<"+type+" class='"+css_class+"'>"+x+"</"+type+">";
}
else {
	ele = "<"+type+" class='"+css_class+"' style='font-size:"+size+"; font-weight:"+weight+"; color:"+color+"; cursor:"+cursor+";'>"+x+"</"+type+">";
}
 
//Get the standard font-color of the used style
st_color = $("label").css("color");
 
//insert the X before the checkboxes
$(document).ready(function() { 
	$("li").each(function(){
		$(this).prepend(ele);
	});
});
 
//bind an eventhandler on the X (delegate is used, because Limesurvey uses an old jQuery version)
var css_class_c = "."+css_class;
$(document).delegate(css_class_c,"click",function() {
	var color_check = $(this).siblings("label").data("color");
	if(color_check != "true") {
    	$(this).siblings("label").css("color",label_color).data("color","true");
    	$(this).siblings('input').attr('checked', false);
 
	}
	else {
		$(this).siblings("label").css("color",st_color).data("color","false");
 
	}
});
 
$(".checkbox").click(function () {
	$(this).siblings("label").css("color",st_color).data("color","false");
});
 
</script>


Prevent the survey from being submitted when users press enter

By default HTML forms are submitted when the user presses enter in any text field. This can be annoying with long survey pages or if your users are inexperienced with keyboard shortcuts.

To disable this behavior, add the following code snippet to template.js:

$(document).ready(function(){
    $('input').keypress(
    function(event){
     if (event.which == '13') {
        event.preventDefault();
      }
    });
});

Card Sorting

Version 2.06

Tested with: 2.06,

This workaround uses JavaScript to allow dragging items into "buckets" to "card sort" them. A multiple-short-text question is used with subquestions being the draggable items. The data recorded for each item is the "bucket" number that it was dropped in. "Buckets" are pre-defined in a list in the "Help" section of the question.



DOWNLOADS:
- Demo template
- Demo survey.

IMPLEMENTATION:

1) Set up your survey to use JavaScript.

2) Create your multiple-short-text with subquestions.

3) Insert the "bucket" labels into an unordered list in the Question Help section.

4) Add the following to the end of template.js:

function cardSort(qID) {
 
	// Define some stuff...
	var thisQuestion = $('#question'+qID);
	var thisQuestionHelp = $('img[alt="Help"]', thisQuestion).parent();	
	thisQuestion.addClass('card-sort-question');
 
	// Hide the "Help" section
	thisQuestionHelp.hide();
 
	//Insert the "card sort" elements
	$('ul.questions-list', thisQuestion).parent().prepend('<div class="droppable items-start"></div><div class="items-end-wrapper" />');
	$('ul:eq(0) li', thisQuestionHelp).each(function(i) {
		$('.items-end-wrapper', thisQuestion).append('<div class="items-end-inner">\
													<div class="items-end-text">'+$(this).html()+'</div>\
													<div class="droppable items-end items-end-'+(i+1)+'" data-items-end="'+(i+1)+'"></div>\
												</div>')});
 
	// Insert the "cards"
	$('li.answer-item', thisQuestion).each(function(i) {
		var thisSGQA = $(this).attr('id').replace(/javatbd/, '');
		var thisCode = $(this).attr('id').split('X'+qID)[1];
		var thisHTML = $('label', this).html();
		$('div.items-start').append('<div class="card draggable" data-sgqa="'+thisSGQA+'" data-code="'+thisCode+'">\
										'+thisHTML+'\
									</div>');
	});			
 
	// Make the "cards" draggable
	$('.draggable').draggable({ 
		revert: "invalid", 
		zIndex: 2700, 
		helper: 'original',
		start: function( event, ui ) {
			$(ui.helper).addClass('ui-draggable-helper');
		},
		stop: function( event, ui ) {
		}
	});
 
	// Set the targets for the draggables
	$('.droppable.items-start').droppable({ 
		hoverClass: 'target-hover', 
		accept: '.draggable.moved' 
	});
	$('.droppable.items-end').droppable({ 
		hoverClass: 'target-hover', 
		accept: '.draggable' 
	});
 
	// After dropped
	$('.droppable').bind('drop', function(event, ui) {
 
		// Physically move the draggable to the target 
		// (the plugin just visually moves it)
		// (need to use a clone here to fake out iPad)
		var newDraggable = $(ui.draggable).clone();
		$(newDraggable).appendTo(this);
		$(ui.draggable).remove();
		if($(this).hasClass('items-end')) {
			$(newDraggable).addClass('moved');
		}
		else {
			$(newDraggable).removeClass('moved');
		}
		$(newDraggable).removeClass('ui-draggable-helper ui-draggable-dragging').css({
			'z-index':'',
			'top':'auto', 
			'left':'auto'
		});
 
		// Now, make this new clone draggable
		$(newDraggable).draggable({ 
			revert: "invalid", 
			zIndex: 2700, 
			helper: 'original',
			start: function( event, ui ) {
				$(ui.helper).addClass('ui-draggable-helper');
			},
			stop: function( event, ui ) {
			}
		});
	});
 
	// Initial "card" positions
	$('li.question-item input.text', thisQuestion).each(function(i) {
		if($(this).val() > 0) {
			$('.items-end[data-items-end="'+$(this).val()+'"]').append($('.card[data-sgqa="'+$(this).attr('name')+'"]'));
			$('.card[data-sgqa="'+$(this).attr('name')+'"]').appendTo($('.items-end[data-items-end="'+$(this).val()+'"]')).addClass('moved');
		}
	});
 
	// Interrupt the Next/Submit function and load the inputs
	$('form#limesurvey').submit(function(){
		$('.question-item input.text', thisQuestion).val(0);
		$('.droppable.items-end .card', thisQuestion).each(function(i) {
			var thisItemsEnd = $(this).closest('.droppable.items-end').attr('data-items-end');
			var thisID = $(this).attr('data-sgqa');
			$('#answer'+thisID+'').val(thisItemsEnd);
		});
	});
}


5) Add the following to the end of template.css:

/* Card Sort */
 
.card-sort-question ul.subquestions-list {
	/*display: none;
	clear: both;*/
}
 
.card-sort-question .items-start {
	float: left;
	width: 340px;
	height: 317px;
	margin-top: 8px;
	border: 1px solid #666;
	-moz-border-radius: 6px;
	-webkit-border-radius: 6px;
	-khtml-border-radius: 6px;
	border-radius: 6px;
}
 
.card-sort-question .items-start.target-hover {
	background:#C9C9C9;
}
 
.card-sort-question .items-end-wrapper {
	float: left;
	margin-left: 20px;
	width: 340px;
}
 
.card-sort-question .items-end {
	width: 338px;
	min-height: 73px;
	margin-bottom: 10px;
	padding-bottom: 5px;
	border: 1px solid #666;
	-moz-border-radius: 6px;
	-webkit-border-radius: 6px;
	-khtml-border-radius: 6px;
	border-radius: 6px;
	background: #EBEBEB;
}
 
.card-sort-question .items-end.target-hover {
	background: #C9C9C9;
}
 
.card-sort-question .items-end.ui-state-disabled {
    opacity: 1;
	filter:alpha(opacity=100);
}
 
.card-sort-question .items-end-text {
	width: 338px;
	padding-bottom: 5px;
	background: #FFFFFF;
	font-size: 110%;
	font-weight: bold;
}
 
.card-sort-question .card  {
	display: inline-block;
	width: 140px;
	height: auto;
	margin: 5px 8px;
	padding: 3px;
    border: 2px solid #2F4354;
	-moz-border-radius: 8px;
	-webkit-border-radius: 8px;
	-khtml-border-radius: 8px;
	border-radius: 8px;
	background-color: #43b3d1;
	color: #2f4354;
	font-weight: bold;
	text-align: center;
	line-height: normal;
}
 
.card-sort-question .items-end .card  {
	margin: 5px 7px;
}
 
.card-sort-question  div.answer {
	clear: both;
	padding-top: 1px;
	margin-top: 0;
}


6) Add this to the source of the question to apply the function.

<script type="text/javascript" charset="utf-8">	
	$(document).ready(function() {
		cardSort({QID});
    });
</script>

Version 3.x

Here is a custom question theme for card Sorting in version 3.x. - https://github.com/tpartner/LimeSurvey-Card-Sorting-3.x

Version 4.x

Here is a custom question theme for card Sorting in version 4.x. - https://github.com/tpartner/LimeSurvey-Card-Sorting-4.x

Q Sorting

This workaround is heavily based on the Card Sorting above. It uses JavaScript to allow dragging items into "buckets" to "card sort" them with the additional twist to limit the cards per bucket. A multiple-short-text question is used with subquestions being the draggable items. The data recorded for each item is the "bucket" number that it was dropped in. "Buckets" and their limits are pre-defined in a list in the "Display" section of the question.

Version 3.x

Tested with: 3.08 - 3.15 Custom question theme - Media:QSort.zip

Version 4.x

Tested with: 4.5.1 Custom question theme - Media:qSort-4.x.zip

Text Input (e.g. "short free text", "huge free text"): Force UpperCase + onBlur Trim

Add this to the source of your target question to apply the function.

Here is the primary JavaScript/jQuery coding:

<script type='text/javascript' charset='utf-8'>
$( document ).ready( function() {
	var this_surveyId='{SID}';
	var this_groupId='{self.gid}';
	var this_quesitonId='{self.qid}';
	var this_self='{self.sgqa}';

	// Remark for the variable 'my_targetTextBox':
	// ------------------------------------
	// For Text-type question (e.g. 'short free text', 'huge free text'), use : '#answer' + this_self;
	// For 'Multiple Choice'-type question enabled with the 'Other' option which provides a textbox, use : '#answer' + this_self;
	// For 'List (radio)'-type question enabled with the 'Other' option which provides a textbox, use : '#answer' + this_self + 'text'.
	var my_targetTextBox='#answer' + this_self;


//	Trim:
//	-----
	$( my_targetTextBox ).blur( function(){
		$( this ).val( $.trim( $( this ).val() ) );
	});

//	Uppercase:
//	----------
	// Operate for its appearance only (but have no effect to modify its intrinsic value) - display as UpperCase:
	$( my_targetTextBox ).css( 'text-transform', 'uppercase' );
	// Operate for its intrinsic value:
	$( my_targetTextBox ).blur( function() {
		$( this ).val( $( this ).val().toUpperCase() );
	});
});
</script>

For Text-type question (e.g. 'short free text', 'huge free text'), set the 'this_self' variable as below:

var my_targetTextBox='#answer' + this_self;

For 'Multiple Choice'-type question enabled with the 'Other' option which provides a textbox, set the 'this_self' variable as below:

var my_targetTextBox='#answer' + this_self;


For 'List (radio)'-type question enabled with the 'Other' option which provides a textbox, set the 'this_self' variable as below:

var my_targetTextBox='#answer' + this_self + 'text';

Convert arrays to dropdowns on smaller screens

Tested with: 2.05, IE 8-11, Firefox, Chrome

Here's a little jQuery plugin that converts arrays to drop-downs on smaller screens.

IMPLEMENTATION:

1) Add the following to the end of template.js:

$(document).ready(function(){
    // Apply the responsiveArray plugin with default settings
    $('.array-10-pt, .array-flexible-row, .array-5-pt, .array-increase-same-decrease, .array-yes-uncertain-no').responsiveArray();
});
 
/***** 
    A plugin to insert drop-downs into arrays for small screens
    Copyright (C) 2015 - Tony Partner (http://partnersurveys.com)
    Licensed MIT, GPL
    Version - 2.0
    Create date - 09/04/15
*****/
(function( $ ){
    $.fn.responsiveArray = function(options) {
    
        var opts = $.extend( {
            chooseText: 'Please choose...'            
        }, options);
 
        return this.each(function() {
 
            var thisQuestion = $(this);
            var thisQID = $(thisQuestion).attr('id').split('question')[1];

            // Some classes
            $(thisQuestion).addClass('responsive-array');
            $('table.questions-list tr', thisQuestion).each(function(i){
                $('> *', this).each(function(i){
                    $(this).addClass('col-'+i);
                    if(i != 0) {
                        $(this).addClass('expanded');
                    }
                });
            });

            // Insert a column
            $('.col-0', thisQuestion).after('<td class="dropdown-cell" />');

            // Insert dropdowns
            $('body').append('<select style="display:none;" class="responsive-select responsive-select-'+thisQID+'" />');
            $('table.questions-list thead th.expanded', thisQuestion).each(function(i){
                $('.responsive-select-'+thisQID).append('<option value="">'+$(this).text()+'</option>');
            });
            $('table.questions-list tbody .dropdown-cell', thisQuestion).append($('.responsive-select-'+thisQID+'').clone());
            $('tr.radio-list', thisQuestion).each(function(i){
                var thisRow = $(this);
                $('input.radio', this).each(function(i){
                    $('.responsive-select-'+thisQID+' option:eq('+i+')', thisRow).attr('value', $(this).attr('id'));
                });                    
                if($('input.radio:checked', thisRow).length > 0) {
                    $('.responsive-select-'+thisQID+'', thisRow).val($('input.radio:checked', thisRow).attr('id'));
                }
                else {
                    $('.responsive-select-'+thisQID+'', thisRow).prepend('<option value="" selected="selected">'+opts.chooseText+'</option>');
                }
            });
            $('.responsive-select-'+thisQID+'', thisQuestion).show();

            // Listeners on radios
            $('input.radio', thisQuestion).click(function(event) {
                var thisRow = $(this).closest('tr');
                var thisID = $(this).attr('id');
                //$('option[value="'+thisID+'"]').attr('selected', 'selected');
                $('.responsive-select', thisRow).val(thisID);
                $('.responsive-select option[value=""]', thisRow).remove();
            });

            // Listeners on dropdowns
            $('.responsive-select-'+thisQID+'').change(function() {
                $('#'+$(this).val()+'').click();
            });
 
        });
    };
})( jQuery );


2) Add something like this to template.css (I have it set to switch at 640px but adjust that as you see fit):

/***** 
    Styles for the responsiveArray plugin
    Copyright (C) 2015 - Tony Partner (http://partnersurveys.com)
    Licensed MIT, GPL
    Version - 2.0
    Create date - 09/04/15
*****/
 
.responsive-array .dropdown-cell {
    display: none;
    text-align: left;
    padding-left: 8px;
}
 
.responsive-array .dropdown-cell select {
        max-width: none;
}
 
@media screen and (max-width: 640px) {
    .responsive-array table.question {
        table-layout: auto;
    }
    .responsive-array table.question .col-0,
    .responsive-array table.question .dropdown-cell {
        width: 50%;
    }
    .responsive-array .dropdown-cell {
        display: table-cell;
    }
    .responsive-array th.expanded,
    .responsive-array td.expanded {
        display: none;
    }
}


Note that the "Please choose" text is an optional setting. To change it, apply the plugin like this:

$(document).ready(function(){
    // Apply the responsiveArray plugin with default settings
    $('.array-10-pt, .array-flexible-row, .array-5-pt, .array-increase-same-decrease, .array-yes-uncertain-no').responsiveArray({
        chooseText: 'Select one...'
    });
});

Van Westendorp Pricing Sliders

Version 2.06

Tested with: LimeSurvey version 2.06

This workaround uses JavaScript to dynamically control sliders to facilitate the Van Westendorp pricing research methodology. When a slider is manipulated, all of the "higher" sliders are dynamically pushed to higher levels and "lower" sliders are pushed to lower levels. This ensures that the data will always be valid.



Download a working template and survey (un-zip the package and install the template before the survey).

Implementation:

  1. Set up your survey to use JavaScript.
  2. Create your Multiple Numeric question with subquestions as seen in the image above.
  3. Add the following to the end of template.js:
    function shuffleArray(array) {
    	for (var i = array.length - 1; i > 0; i--) {
    		var j = Math.floor(Math.random() * (i + 1));
    		var temp = array[i];
    		array[i] = array[j];
    		array[j] = temp;
    	}
    	return array;
    }
    
    	
    /*****
        A jQuery plugin to facilitate the Van Westendorp pricing research methodology
        Copyright (C) 2016 - Tony Partner (http://partnersurveys.com)
        Licensed MIT, GPL
        Version - 2.0
        Create date - 19/10/16
    *****/
    (function( $ ){
    
    	$.fn.vWPricing = function(options) {  
    	
    		var opts = $.extend( {
    			order: [1, 2, 3, 4],
    			randomOrder: false
    		}, options);
    		
    		return this.each(function() { 
    				
    			var thisQuestion = $(this);
    			
    			thisQuestion.addClass('vwp-question');
    			
    			// Add some attributes and set the slider max/min values
    			var itemsLength = $('li.question-item', thisQuestion).length;
    			$('li.question-item', thisQuestion).each(function(i) {
    				$(this).attr('data-item', (i+1));
    				$('.ui-slider', this).attr('data-slider', (i+1));
    				
    				var thisSliderMin = $('.ui-slider', this).slider('option', 'min');
    				$('.ui-slider', this).slider('option', 'min', thisSliderMin+(i));
    				var thisSliderMax = $('.ui-slider', this).slider('option', 'max');
    				$('.ui-slider', this).slider('option', 'max', thisSliderMax-((itemsLength-1)-i));
    			});
    			
    			// Slider order
    			if(opts.randomOrder == true) {
    				shuffleArray(opts.order);
    			}
    			$(opts.order).each(function(i, val) {
    				$('ul.subquestions-list', thisQuestion).append($('li.question-item[data-item="'+val+'"]', thisQuestion));
    			});
    	
    			// Listeners on the sliders
    			$('.ui-slider', thisQuestion).on('slide', function(event, ui) {
    				handleVwpSliders(this, ui.value);
    			});	
    			$('.ui-slider', thisQuestion).on('slidestop', function(event, ui) {
    				handleVwpSliders(this, ui.value);
    			});
    			
    			// A function to handle the siders
    			function handleVwpSliders(el, sliderVal) {
    				var thisRow = $(el).closest('li');
    				var movedIndexNum = $(el).attr('data-slider');
    				
    				$('input.text', thisRow).val(sliderVal);
    				$('.slider-callout', thisRow).html($('input.text', thisRow).val());
    				
    				var lowerSliders = $('.ui-slider', thisQuestion).filter(function() {
    					return $(this).attr('data-slider') < movedIndexNum;
    				});
    				var higherSliders = $('.ui-slider', thisQuestion).filter(function() {
    					return $(this).attr('data-slider') > movedIndexNum;
    				});
    				
    				$(lowerSliders).each(function(i) {
    					var thisIndexNum = $(this).attr('data-slider');
    					var newSliderVal = sliderVal-(movedIndexNum-thisIndexNum);
    					if($(this).slider('option', 'value') > newSliderVal) {
    						var thisRow = $(this).closest('li');
    						$(this).slider('option', 'value', newSliderVal);
    						$('input.text', thisRow).val(newSliderVal);
    						$('.slider-callout', thisRow).html($('input.text', thisRow).val());
    					}
    				});
    				$(higherSliders).each(function(i) {
    					var thisIndexNum = $(this).attr('data-slider');
    					var newSliderVal = sliderVal+(thisIndexNum-movedIndexNum);
    					if($(this).slider('option', 'value') < newSliderVal) {
    						var thisRow = $(this).closest('li');
    						$(this).slider('option', 'value', newSliderVal);
    						$('input.text', thisRow).val(newSliderVal);
    						$('.slider-callout', thisRow).html($('input.text', thisRow).val());
    					}
    				});
    			}
    		});
    	
    	};
    })( jQuery );
  4. Add the following to the end of template.css (this is for a copy of Denis Chenu's SkeletonQuest template):
    /**** Van Westendorp Pricing ****/
    
    .vwp-question ul.subquestions-list,
    .vwp-question ul.subquestions-list li {
        display:block;
    }
    
    .vwp-question ul.subquestions-list li {
    	margin-bottom: 15px;
    }
    
    .vwp-question ul.subquestions-list .slider-label {
        display:block;
        padding: 0.3em 0.5em 0.7em;
        width: auto;
    	max-width: 100%;
    	text-align: left;
    }
    
    .vwp-question .slider-element {
        display: block;
    	margin-left: 2%;
    	width:90%;
    	max-width: 32em;
    }
  5. To apply the plugin, add this script the multiple-numeric question source.

There are two options to adjust
-order - this is the order that your subquestions will be displayed (default, 1, 2, 3, 4)
-randomOrder - if set to true, the order option will be overridden and the subquestion order will be randomized (default, false)

<script type="text/javascript" charset="utf-8">		
	$(document).ready(function(){
		
		$('#question{QID}').vWPricing({
			order: [1, 2, 3, 4],
			randomOrder: false
		});
		
	});
</script>

Version 2.5x

Tested with: LimeSurvey version 2.54.5

This workaround uses JavaScript to dynamically control sliders to facilitate the Van Westendorp pricing research methodology. When a slider is manipulated, all of the "higher" sliders are dynamically pushed to higher levels and "lower" sliders are pushed to lower levels. This ensures that the data will always be valid.



Download a working template and survey (un-zip the package and install the template before the survey).

Implementation:

  1. Set up your survey to use JavaScript.
  2. Create your Multiple Numeric question with subquestions as seen in the image above.
  3. Add the following to the end of template.js:
    function shuffleArray(array) {
    	for (var i = array.length - 1; i > 0; i--) {
    		var j = Math.floor(Math.random() * (i + 1));
    		var temp = array[i];
    		array[i] = array[j];
    		array[j] = temp;
    	}
    	return array;
    }
    
    	
    /*****
        A jQuery plugin to facilitate the Van Westendorp pricing research methodology
        Copyright (C) 2016 - Tony Partner (http://partnersurveys.com)
        Licensed MIT, GPL
        Version - 2.0
        Create date - 19/10/16
    *****/
    (function( $ ){
    
    	$.fn.vWPricing25 = function(options) {  
    	
    		var opts = $.extend( {
    			order: [1, 2, 3, 4],
    			randomOrder: false
    		}, options);
    		
    		return this.each(function() { 
    				
    			var thisQuestion = $(this);
    			
    			thisQuestion.addClass('vwp-question');
    			
    			// Add some attributes and set the slider max/min values
    			var itemsLength = $('.question-item', thisQuestion).length;
    			$('.question-item', thisQuestion).each(function(i) {
    				var thisInput = $('input[type=text]', this);
    				var thisVal = $(thisInput).val();
    				
    				$(this).attr('data-item', (i+1));
    				$(thisInput).attr('data-slider-index', (i+1));
    		
    				// Slider initial settings
    				setTimeout(function() {	
    					var thisSliderMin = $(thisInput).bootstrapSlider('getAttribute', 'min');
    					$(thisInput).bootstrapSlider('setAttribute', 'min', thisSliderMin+(i));
    					var thisSliderMax = $(thisInput).bootstrapSlider('getAttribute', 'max');
    					$(thisInput).bootstrapSlider('setAttribute', 'max', thisSliderMax-((itemsLength-1)-i));
    					
    					if(thisVal == '') {
    						$(thisInput).val('');
    					}
    				}, 200);
    				
    			});
    			
    			// Slider order
    			if(opts.randomOrder == true) {
    				shuffleArray(opts.order);
    			}
    			$(opts.order).each(function(i, val) {
    				$('.subquestion-list.questions-list', thisQuestion).append($('.question-item[data-item="'+val+'"]', thisQuestion));
    			});
    	
    			// Listeners on the sliders
    			$('input[type=text]', thisQuestion).on('slide', function(event) {
    				handleVwpSliders(this, $(this).bootstrapSlider('getValue'));
    			});	
    			$('input[type=text]', thisQuestion).on('slideStop', function(event) {
    				handleVwpSliders(this, $(this).bootstrapSlider('getValue'));
    			});
    			
    			// A function to handle the siders
    			function handleVwpSliders(el, sliderVal) {
    				var movedIndexNum = $(el).attr('data-slider-index');
    				
    				var lowerSliders = $('input[type=text]', thisQuestion).filter(function() {
    					return $(this).attr('data-slider-index') < movedIndexNum;
    				});
    				var higherSliders = $('input[type=text]', thisQuestion).filter(function() {
    					return $(this).attr('data-slider-index') > movedIndexNum;
    				});
    				
    				$(lowerSliders).each(function(i) {
    					var thisIndexNum = $(this).attr('data-slider-index');
    					var newSliderVal = sliderVal-(movedIndexNum-thisIndexNum);
    					if($(this).bootstrapSlider('getValue') > newSliderVal) {
    						var thisRow = $(this).closest('.question-item');
    						var thisTooltip = $('.tooltip', thisRow);
    						$(this).bootstrapSlider('setValue', newSliderVal);
    						$('.tooltip-inner', thisTooltip).text($('input.text', thisRow).val());
    						thisTooltip.show().css('margin-left', '-'+(thisTooltip.width()/2)+'px');
    						
    						var validationInput =  $('input.em_sq_validation', thisRow);
    						$(validationInput).val($('input.text', thisRow).val())
    						fixnum_checkconditions($(validationInput).attr('value'), $(validationInput).attr('name'), $(validationInput).attr('type'));
    					}
    				});
    				$(higherSliders).each(function(i) {
    					var thisIndexNum = $(this).attr('data-slider-index');
    					var newSliderVal = sliderVal+(thisIndexNum-movedIndexNum);
    					if($(this).bootstrapSlider('getValue') < newSliderVal) {
    						var thisRow = $(this).closest('.question-item');
    						var thisTooltip = $('.tooltip', thisRow);
    						$(this).bootstrapSlider('setValue', newSliderVal);
    						$('.tooltip-inner', thisTooltip).text($('input.text', thisRow).val());
    						thisTooltip.show().css('margin-left', '-'+(thisTooltip.width()/2)+'px');
    						
    						var validationInput =  $('input.em_sq_validation', thisRow);
    						$(validationInput).val($('input.text', thisRow).val())
    						fixnum_checkconditions($(validationInput).attr('value'), $(validationInput).attr('name'), $(validationInput).attr('type'));
    					}
    				});
    			}
    		
    			// Listener on resizing (override the bootstrap callout behaviour)
    			$(window).resize(function() {
    				setTimeout(function() {	
    					$('input[type=text]', thisQuestion).each(function(i) {
    						if($(this).val() != '') {
    							var thisRow = $(this).closest('.question-item');
    							var thisTooltip = $('.tooltip', thisRow);
    							$('.tooltip-inner', thisTooltip).text($.trim($(this).val()));
    							console.log($('.tooltip-inner', thisTooltip).text());
    							thisTooltip.show().css('margin-left', '-'+(thisTooltip.width()/2)+'px');
    						}
    					});
    				}, 1);
    			});
    		});	
    	};
    })( jQuery );
  4. Add the following to the end of template.css (this is for a copy of the default template):
    /*********** Van Westendorp Pricing ***********/
    
    .vwp-question .control-label {
        margin-bottom: 35px;
    }
    
    .vwp-question .withslider {
        margin-bottom: 0px;
    }
    
    .vwp-question .slider-container {
        margin-bottom: 2.5em;
        margin-top: 3.5em;
    }
    
    .vwp-question .slider.slider-horizontal .slider-handle {
        margin-top: -5px;
    }
    
    @media (max-width: 600px){
    
    	.vwp-question .withslider {    
    		padding-left: 0;
    		padding-right: 0;
    	}
    }
    
    @media (max-width: 480px){
    
    	#outerframeContainer,
    	.vwp-question .control-label,
    	.vwp-question .slider-container > div {    
    		padding-left: 0;
    		padding-right: 0;
    	}
    	
    	.vwp-question .slider-container > div > div {    
    		padding-left: 5px;
    		padding-right: 5px;
    	}
    }
  5. To apply the plugin, add this script the multiple-numeric question source.

There are two options to adjust
-order - this is the order that your subquestions will be displayed (default, 1, 2, 3, 4)
-randomOrder - if set to true, the order option will be overridden and the subquestion order will be randomized (default, false)

<script type="text/javascript" charset="utf-8">		
	$(document).ready(function(){
		
		$('#question{QID}').vWPricing25({
			order: [1, 2, 3, 4],
			randomOrder: false
		});
		
	});
</script>

Version 3.x

A custom question theme for Pricing Sliders can be found here - https://github.com/tpartner/LimeSurvey-Pricing-Sliders-3.x

Multiple Choice - Select "All of the above"

Version 2.67.x

Tested with: LimeSurvey version 2.67.3

This script will render the last item of a multiple-choice question as "Select all".

Download a sample survey.

Implementation:

  1. Set up your survey to use JavaScript.
  2. Create your Multiple Choice question with the last subquestions as "Select all".
  3. Add the following script to the source of the question text:
    <script type="text/javascript" charset="utf-8">
    	$(document).on('ready pjax:complete',function() {
    		
    		// Identify this question
    		var thisQuestion = $('#question{QID}');
    		
    		// Initial states
    		if($('input[type="checkbox"]:last', thisQuestion).is(':checked')) {
    			$('input[type="checkbox"]', thisQuestion).not(':last').prop('checked', true).prop('disabled', true);
    		}
    	
    		// Listener on the last checkbox
    		$('input[type="checkbox"]:last', thisQuestion).on('change', function(e) {
    			if($(this).is(':checked')) {
    				$('input[type="checkbox"]', thisQuestion).not(this).prop('checked', true).prop('disabled', true);
    			}
    			else {
    				$('input[type="checkbox"]', thisQuestion).not(this).prop('checked', false).prop('disabled', false);
    			}
    			
    			// Fire ExpressionScript
    			$('input[type="checkbox"]', thisQuestion).not(this).each(function(i) {
    				checkconditions(this.value, this.name, this.type);
    			});
    		});
     
    		// Re-enable checkboxes so data gets stored
    		$('#moveprevbtn, #movenextbtn, #movesubmitbtn').on('click', function(){
    			$('input[type="checkbox"]', thisQuestion).prop('disabled', false);
    		});
    	});
    </script>

Pay Off Matrix Interface

LimeSurvey Version 2.67.x

This workaround converts a list-radio type question into a Pay Off Matrix interface.




Download a working template and survey (un-zip the package and install the template before the survey).

Implementation:

  1. Create your List Radio question with with the upper and lower values for each answer separated by "pipe" characters.
  2. Assign a class "pay-off-matrix" to that question.
  3. Add the following to the end of template.js:
    $(document).on('ready pjax:complete',function() {
    	// Apply the payOffMatrix plugin
    	$('.pay-off-matrix').payOffMatrix({
    			topText:	'You receive', 
    			bottomText:	'Others receive' 
    	});
    });
    	
    /*****
        A jQuery plugin to convert a list-radio question to a Pay Off Matrix
        Copyright (C) 2017 - Tony Partner (http://partnersurveys.com)
        Licensed MIT, GPL
        Version - 1.0
        Create date - 25/09/2017
    *****/
    
    (function($){
     
    	$.fn.payOffMatrix = function(options) {  
     
    		var opts = $.extend( {
    			topText:	'', 
    			bottomText:	'' 
    		}, options);
     
    		return this.each(function() {
    			
    			var thisQuestion = $(this);
    
    			// Insert the matrix elements
    			$('div.answer', thisQuestion).prepend('<div class="pom-outer-wrapper"><div class="pom-wrapper">\
    													<div class="pom-row-label top">'+opts.topText+'</div>\
    													<div class="pom-items-wrapper">\
    													</div>\
    													<div class="pom-row-label bottom">'+opts.bottomText+'</div>\
    													<div style="clear:both;" />\
    												</div></div>');
    			
    			$('.answer-item', thisQuestion).each(function(i) {
    				var thisValue = $('input.radio', this).attr('value');
    				var itemTop = $.trim($('.label-text', this).text()).split('|')[0];
    				var itemBottom = $.trim($('.label-text', this).text()).split('|')[1];
    				$('.pom-items-wrapper', thisQuestion).append('<div class="pom-item">\
    																<div class="pom-item-label-wrapper top">\
    																	<div class="pom-item-label">'+itemTop+'</div>\
    																</div>\
    																<div class="pom-item-button-wrapper">\
    																	<div class="pom-item-button" data-value="'+thisValue+'"></div>\
    																</div>\
    																<div class="pom-item-label-wrapper bottom">\
    																	<div class="pom-item-label">'+itemBottom+'</div>\
    																</div>\
    																<div class="under-480" style="clear:both;" />\
    															</div>');
    			});
    			$('.pom-item:first', thisQuestion).addClass('first');
    			$('.pom-item:last', thisQuestion).addClass('last');
    			
    			// Listeners on the matrix events
    			$('.pom-item-button, .pom-item-label', thisQuestion).on('click', function(e) {
    				$('.pom-item', thisQuestion).removeClass('active-item');
    				$(this).closest('.pom-item').addClass('active-item');
    				var thisValue = $(this).closest('.pom-item').find('.pom-item-button').attr('data-value');
    				$('input.radio[value="'+thisValue+'"]', thisQuestion).trigger('click');
    			}).hover(
    				function() {
    					$(this).closest('.pom-item').addClass('hover');
    				}, function() {
    					$( this ).closest('.pom-item').removeClass('hover');
    				}
    			);
    		});
     
    	};
    })(jQuery);
  4. Add the following to the end of template.css:
    /***** 
        Styles for the payOffMatrix plugin
        Copyright (C) 2017 - Tony Partner (http://partnersurveys.com)
        Licensed MIT, GPL
        Version - 1.0
        Create date - 25/09/2017
    *****/
    
    .pay-off-matrix .answers-list {
    	position: absolute;
    	left: -9999em;
    }
    
    .pom-wrapper {
    	position: relative;
    	margin: 0 auto;
    	width: 100%;
    	max-width: 648px;
    }
    
    .pom-item {
    	float: left;
    }
    
    .pom-row-label {
    	clear: both;
    	text-align: center;
    	font-size: 16px;
    }
    
    .pom-row-label.top {
    	padding-top: 5px;
    }
    
    .pom-row-label.bottom {
    	padding-bottom: 5px;
    }
    
    .pom-item-label-wrapper {
    	padding: 5px 8px;
    }
    
    .pom-item-label-wrapper.top {
    	margin-bottom: 3px;
    }
    
    .pom-item-label-wrapper.bottom {
    	margin-top: 3px;
    }
    
    .pom-item {
    	width: 11.11111111111111%;
    }
    
    .pom-item-label {
    	border: 1px solid #233140;
    	background: #EBEBEB;
    	-moz-transition: background-color 300ms ease; 
    	-o-transition: background-color 300ms ease; 
    	-webkit-transition: background-color 300ms ease; 
    	transition: background-color 300ms ease;
    	text-align:center;
    	-moz-border-radius: 5px;
    	-webkit-border-radius: 5px;
    	-khtml-border-radius: 5px;
    	border-radius: 5px;
    	font-size: 16px;
    	line-height: 30px;
    }
    
    .pom-item.hover .pom-item-label,
    .pom-item.active-item .pom-item-label {
    	font-weight: bold;
    	cursor: pointer;
    }
    
    .pom-item.active-item .pom-item-label {
    	background: #233140;
    	color: #FFFFFF;
    }
    
    .pom-item-button-wrapper {
    	padding: 5px 15.27777777777778%;
        background: #CCD5DD;
    	width: 69.44444444444444%;
    	width: auto; 
    }
    
    .pom-item.first .pom-item-button-wrapper {
    	-moz-border-radius: 5px 0 0 5px;
    	-webkit-border-radius: 5px 0 0 5px;
    	-khtml-border-radius: 5px 0 0 5px;
    	border-radius: 5px 0 0 5px;
    }
    
    .pom-item.last .pom-item-button-wrapper {
    	-moz-border-radius: 0 5px 5px 0;
    	-webkit-border-radius: 0 5px 5px 0;
    	-khtml-border-radius: 0 5px 5px 0;
    	border-radius: 0 5px 5px 0;
    }
    
    .pom-item-button {
        height: 0;
        width: 100%;
    	padding-top: 100%;
        background: #FFFFFF;
    	-moz-transition: background-color 300ms ease; 
    	-o-transition: background-color 300ms ease; 
    	-webkit-transition: background-color 300ms ease; 
    	transition: background-color 300ms ease;
    	-moz-border-radius: 50%;
    	-webkit-border-radius: 50%;
    	-khtml-border-radius: 50%;
    	border-radius: 50%;
        cursor: pointer;
    }
    
    .pom-item.hover .pom-item-button {
        background-color: #5b7fa5;
    }
    
    .pom-item.active-item .pom-item-button,
    .pom-item.active-item .pom-item-button:hover {
    	background-color: #233140;
    	cursor:default;
    }
    
    @media only screen and (max-width: 480px) {
    	.pom-outer-wrapper {
    		margin: 0 auto;
    		padding-top: 50px;
    		width: 256px; 
    	}
    	.pom-wrapper {
    		display:block;
    		position: relative;
    	}
    	
    	.pom-row-label {
    		bottom: 100%;
    		left: 0;
    		margin-left: -25px;
    		position: absolute;
    		text-align: center;
    		width: 150px;
    		background-color: transparent !important;
    	}
    	
    	.pom-row-label.bottom {
    		left: auto;
    		right: 0;
    		margin-right: -25px;
    		padding: 5px 0 0 0;
    	}
    	
    	pom-items-wrapper {
    		vertical-align: middle;
    	}
    	
    	.pom-item {
    		float: none;
    		width: auto;
    	}
    	
    	.pom-item-label-wrapper {
    		float: left;
    		margin: 0 !important;
    		padding: 7px 24px;
    	}
    	
    	.pom-item-label {
    		width: 50px; 
    	}
    	
    	.pom-item-button-wrapper {
    		float: left;
    		margin: 0 5px;
    		padding: 5px;
    		width: 46px;
    	}
    	
    	.pom-item.first .pom-item-button-wrapper {
    		-moz-border-radius: 5px 5px 0 0;
    		-webkit-border-radius: 5px 5px 0 0;
    		-khtml-border-radius: 5px 5px 0 0;
    		border-radius: 5px 5px 0 0;
    	}
    	
    	.pom-item.last .pom-item-button-wrapper {
    		-moz-border-radius: 0 0 5px 5px;
    		-webkit-border-radius: 0 0 5px 5px;
    		-khtml-border-radius: 0 0 5px 5px;
    		border-radius: 0 0 5px 5px;
    	}
    	
    	.pom-item .under-480 {
    		display: block !important;
    	}
    }

LimeSurvey Version 3.x

Here is a custom question theme for LS 3.x - https://github.com/tpartner/LimeSurvey-Social-Value-Orientation

Number Input Spinner

Tested with: LimeSurvey 3.28

I created this control so that users can indicate their number of days and half days of work. They should not be able to indicate anything other than numbers rounded to 0.5. The original source of this code can be found here: https://codepen.io/famed/pen/gwQPyQ.

How to use it

Save the JavaScript and the CSS code

<script>
'use strict';

function ctrl(numInputId, spinnerId, increment_min = 1, increment_max = 10, maxNumInput = 999999999) {
	let _this = this;
	
	this.setNumInput = function(numVal) {
		let eventChange = new Event('change', { 'bubbles': true, 'cancelable': true });
		document.getElementById(numInputId).value = numVal;
		document.getElementById(numInputId).dispatchEvent(eventChange);
	};
	
	this.counter = 0;
	this.els = {
		decrement_min: document.getElementById(spinnerId).querySelector('.ctrl__button_min_decrement'),
		decrement_max: document.getElementById(spinnerId).querySelector('.ctrl__button_max_decrement'),
		counter: {
			container: document.getElementById(spinnerId).querySelector('.ctrl__counter'),
			num: document.getElementById(spinnerId).querySelector('.ctrl__counter-num'),
			input: document.getElementById(spinnerId).querySelector('.ctrl__counter-input')
		},
		increment_min: document.getElementById(spinnerId).querySelector('.ctrl__button_min_increment'),
		increment_max: document.getElementById(spinnerId).querySelector('.ctrl__button_max_increment')
	};
	
	this.decrement_min = function() {
		let counter = parseFloat(_this.getCounter());
		let nextCounter = (counter-increment_min >= 0) ? counter-increment_min : 0;
		_this.setCounter(nextCounter);
		_this.setNumInput(nextCounter);
	};
	
	this.increment_min = function() {
		let counter = parseFloat(_this.getCounter());
		let nextCounter = ((counter + increment_min) <= maxNumInput) ? counter + increment_min : maxNumInput;
		_this.setCounter(nextCounter);
		_this.setNumInput(nextCounter);
	};
	
	this.decrement_max = function() {
		let counter = parseFloat(_this.getCounter());
		let nextCounter = (counter-increment_max >= 0) ? counter-increment_max : 0;
		_this.setCounter(nextCounter);
		_this.setNumInput(nextCounter);
	};
	
	this.increment_max = function() {
		let counter = parseFloat(_this.getCounter());
		let nextCounter = ((counter + increment_max) <= maxNumInput) ? counter + increment_max : maxNumInput;
		_this.setCounter(nextCounter);
		_this.setNumInput(nextCounter);
	};
	
	this.getCounter = function() {
		return _this.counter;
	};
	
	this.setCounter = function(nextCounter) {
		_this.counter = nextCounter;
	};
	
	this.debounce = function(callback) {
		setTimeout(callback, 50);
	};
	
	this.render = function(hideClassName, visibleClassName) {
		_this.els.counter.num.classList.add(hideClassName);
		
		setTimeout(function() {
			_this.els.counter.num.innerText = _this.getCounter();
			_this.els.counter.input.value = _this.getCounter();
			_this.els.counter.num.classList.add(visibleClassName);
		}, 100);
		
		setTimeout(function() {
			_this.els.counter.num.classList.remove(hideClassName);
			_this.els.counter.num.classList.remove(visibleClassName);
		}, 1100);
	};
	
	this.ready = function() {
		
		let initNum = 0;
		if (document.getElementById(numInputId).value !== "") {
			initNum = document.getElementById(numInputId).value;
		}
		
		_this.counter = initNum;
		_this.els.counter.num.innerText = initNum;
		_this.els.counter.input.value = initNum;
		
		
		_this.els.decrement_min.addEventListener('click', function() {
			_this.debounce(function() {
				_this.decrement_min();
				_this.render('is-decrement-hide', 'is-decrement-visible');
			});
		});
		
		_this.els.increment_min.addEventListener('click', function() {
			_this.debounce(function() {
				_this.increment_min();
				_this.render('is-increment-hide', 'is-increment-visible');
			});
		});
		
		_this.els.decrement_max.addEventListener('click', function() {
			_this.debounce(function() {
				_this.decrement_max();
				_this.render('is-decrement-hide', 'is-decrement-visible');
			});
		});
		
		_this.els.increment_max.addEventListener('click', function() {
			_this.debounce(function() {
				_this.increment_max();
				_this.render('is-increment-hide', 'is-increment-visible');
			});
		});
		
		_this.els.counter.input.addEventListener('input', function(e) {
			
			let parseValue = parseFloat(e.target.value.replace(",","."));
			let pattern = /^[0-9]+[,.]?[0-9]*$/;
			if (pattern.test(e.target.value) && (parseValue % increment_min == 0) && (parseValue <= maxNumInput)) {
				_this.setCounter(parseValue);
				_this.setNumInput(parseValue);
				_this.render();
			} 
			else {
				_this.setCounter(_this.getCounter());
				_this.render();
			}
			
		});
		
		_this.els.counter.input.addEventListener('focus', function(e) {
			_this.els.counter.container.classList.add('is-input');
		});
		
		_this.els.counter.input.addEventListener('blur', function(e) {
			_this.els.counter.container.classList.remove('is-input');
			_this.render();
		});
	};
};
</script> 


<style type="text/css">

.ctrl {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	font-size: 18px;
	margin-top: 10px;
}
.ctrl__counter {
	position: relative;
	width: 100px;
	height: 40px;
	color: #333C48;
	text-align: center;
	overflow: hidden;
	background-color: #fff;
	border-bottom: 1px solid #ccc;
	border-top: 1px solid #ccc;
}
.ctrl__counter.is-input .ctrl__counter-num {
	visability: hidden;
	opacity: 0;
	transition: opacity 100ms ease-in;
}
.ctrl__counter.is-input .ctrl__counter-input {
	visability: visible;
	opacity: 1;
	transition: opacity 100ms ease-in;
}
.ctrl__counter-input {
	width: 100%;
	margin: 0;
	padding: 0;
	position: relative;
	z-index: 2;
	box-shadow: none;
	outline: none;
	border: none;
	color: #333C48;
	font-size: 18px;
	line-height: 40px;
	text-align: center;
	visability: hidden;
	opacity: 0;
	transition: opacity 100ms ease-in;
}
.ctrl__counter-num {
	position: absolute;
	z-index: 1;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
	line-height: 40px;
	visability: visible;
	opacity: 1;
	transition: opacity 1000ms ease-in;
}
.ctrl__counter-num.is-increment-hide {
	opacity: 0;
	transform: translateY(-50px);
	-webkit-animation: increment-prev 100ms ease-in;
	animation: increment-prev 100ms ease-in;
}
.ctrl__counter-num.is-increment-visible {
	opacity: 1;
	transform: translateY(0);
	-webkit-animation: increment-next 100ms ease-out;
	animation: increment-next 100ms ease-out;
}
.ctrl__counter-num.is-decrement-hide {
	opacity: 0;
	transform: translateY(50px);
	-webkit-animation: decrement-prev 100ms ease-in;
	animation: decrement-prev 100ms ease-in;
}
.ctrl__counter-num.is-decrement-visible {
	opacity: 1;
	transform: translateY(0);
	-webkit-animation: decrement-next 100ms ease-out;
	animation: decrement-next 100ms ease-out;
}
.ctrl__button {
	width: 40px;
	line-height: 40px;
	text-align: center;
	color: #fff;
	cursor: pointer;
	background-color: #9aa2a3;
	transition: background-color 100ms ease-in;
}
.ctrl__button:hover {
	background-color: #90a2b0;
	transition: background-color 100ms ease-in;
}
.ctrl__button:active {
	background-color: #778996;
	transition: background-color 100ms ease-in;
}
.ctrl__button_max_decrement {
	border-radius: 5px 0 0 5px;
	border-right: 1px solid #ccc;
}
.ctrl__button_max_increment {
	border-radius: 0 5px 5px 0;
	border-left: 1px solid #ccc;
}

@-webkit-keyframes decrement-prev {
	from {
		opacity: 1;
		transform: translateY(0);
	}
}

@keyframes decrement-prev {
	from {
		opacity: 1;
		transform: translateY(0);
	}
}
@-webkit-keyframes decrement-next {
	from {
		opacity: 0;
		transform: translateY(-50px);
	}
}
@keyframes decrement-next {
	from {
		opacity: 0;
		transform: translateY(-50px);
	}
}
@-webkit-keyframes increment-prev {
	from {
		opacity: 1;
		transform: translateY(0);
	}
}
@keyframes increment-prev {
	from {
		opacity: 1;
		transform: translateY(0);
	}
}
@-webkit-keyframes increment-next {
	from {
		opacity: 0;
		transform: translateY(50px);
	}
}
@keyframes increment-next {
	from {
		opacity: 0;
		transform: translateY(50px);
	}
}
</style>

You can save this code at three different places.

  • In the LimeSurvey template: If you want to use it different surveys.
  • In a group of questions: if it is a group that will include several spinner type questions.
  • In the question itself, if you need only one spinner.

Create the number input spinner with decrement and increment buttons

First yout need to create à "numerical input" question type

Then, in the question field editor, in "source" mode, paste this code

My question ...
<script> 
(function() {	
	let controls = new ctrl("answer{SGQ}", "spinner{SGQ}", 0.5, 5, 100);
	document.addEventListener('DOMContentLoaded', controls.ready);
})();	

$(document).ready(
	function(){
		let answer = document.getElementById("answer{SGQ}");
		if (answer.value == "") {
			answer.value = 0;
		}
		let eventChange = new Event('change', { 'bubbles': true, 'cancelable': true });
		answer.dispatchEvent(eventChange);
		answer.style.display='none';
	}
); 
</script>

You can define increments and max input value changing the parameters when the spinner is instantiated

let controls = new ctrl("answer{SGQ}", "spinner{SGQ}", 0.5, 5, 100);