Actions

Workarounds: Manipulating a survey at runtime using Javascript

From LimeSurvey Manual

Revision as of 15:45, 19 April 2013 by Tpartner (talk | contribs)

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: 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.

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.

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" enclose="div">
  • End tag: </syntaxhighlight>.

How to use Script (eg. JavaScript etc.) in LimeSurvey?

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).ready function to prevent the code from executing until the page is fully loaded:

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

   $(document).ready(function() {

       alert("Test!");

   });

</script>

Sum up input values using Javascript

Tested with:

As of version 1.92, this is easier to do using the sum() function in Expression Manager. See this #Calculate assessment values at runtime and store the results in the survey data|HowTo example.

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 Expression Manager. 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 SQGA 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.

Skipping the welcome page

Tested with: 1.85 (second code snippet)

Note: As of version 1.91, there is a survey option letting you skip the welcome page, so this workaround is not needed.

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:

<script>

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

document.limesurvey.move.value = 'movenext';

document.limesurvey.submit(); });

</script>

It will reproduce the onclick event of the submit button and submit the form.

Drawback of course is that users will see the page flashing up.

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.

Solution 2:

<script>

function custom_on_load(){

  document.limesurvey.move.value = 'movenext';

  document.limesurvey.submit();

}

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

</script>

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<div class="simplebox">="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</div>="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 sub-questions (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 Expression Manager 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 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 #Calculate assessment values at runtime and store the results in the survey data|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.

Reverse array_filter (for pre-1.90)

Tested with: 1.85+ (7253), IE 6/7, FireFox 3.0, Safari 3.2, Chrome 2.0

Note: Version 1.91 natively supports this functionality via the array_filter_exclude advanced quation option.

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 this forum thread and pictured below. There is a live demo here

We'll use the above example to explain the process as follows:

  1. In group 1 - Create a SessionsAttended multi-options question
  2. In group 1 - Create a SessionsNotAttended multi-options question with identical answers as SessionsAttended and hide it with styles or conditions
  3. In group 2 - Create a RateSessionsAttended array question with an array_filter based on SessionsAttended
  4. In group 2 - Create a ReasonNotAttended array question with an array_filter based on SessionsNotAttended
  5. 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:

       // The toggle function

   function toggle(q1, q2) {

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

           $(q2).attr('checked', true);

       }

       else {

           $(q2).attr('checked', false);

       }

   }

   // Initialize the SessionNotAttended checkboxes to "checked"

   toggle ('#answer11111X22X331', '#answer11111X22X441');

   toggle ('#answer11111X22X332', '#answer11111X22X442');

   toggle ('#answer11111X22X333', '#answer11111X22X443');

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

   // The jQuery listeners on the SessionAttended checkboxes

   // that toggle the SessionNotAttended checkboxes

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

       toggle ('#answer11111X22X331', '#answer11111X22X441');

   });

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

       toggle ('#answer11111X22X332', '#answer11111X22X442');

   });

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

       toggle ('#answer11111X22X333', '#answer11111X22X443');

   });

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

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

   });

Some notes about the code:

  • The code is in an onload function defined in the first question of group 1 - see workarounds 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:

  • CSS can be used to hide SessionsNotAttended (div#question44 { display: none; }) if you don't want to use conditions (because maybe you've got $deletenonvalues set to 1)
  • RateSessionsAttended and ReasonNotAttended are another group to avoid things popping in and out of existence as SessionsAttended is answered
  • 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

Tested with: 1.90, IE 7/8, FireFox 3/4, Safari 5.0, Chrome 10.0

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.

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.

There is a demo of both methods here.

Implementation is as follows.

FOR BOTH METHODS:

  • Set up your survey to use JavaScript.
  • Place the following script in your template.js file. This function will be accessed by either method.
    // 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
    
       if(prevPage == 1) {
    
           $('#question'+q1ID+' li[id<div class="simplebox">="javatbd"]:visible input.checkbox').attr('checked', true);
    
           $('#question'+q1ID+'').hide();
    
       }
    
       handleChecked(q1ID, q2ID);
    
       $('#question'+q1ID+' input.checkbox').click(function() {
    
           handleChecked(q1ID, q2ID);
    
       });
    
       function handleChecked(q1ID, q2ID) {
    
           // Find the survey and group IDs
    
           if($( 'input#fieldnames' ).length != 0) {
    
               var fieldNames = $( 'input#fieldnames' ).attr('value');
    
               var tmp = fieldNames.split('X');
    
               var sID = tmp[0];
    
               var gID = tmp[1];
    
           }
    
           // Loop through all Q1 options
    
           $('#question'+q1ID+' input.checkbox').each(function(i) {
    
               // Find the answer code and value
    
               var tmp2 = $(this).attr('id').split('X'+gID+'X'+q1ID+'');
    
               var ansCode = tmp2[1];
    
               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</div>="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<div class="simplebox">="fvalue_"][value="'+ansCode+'"]').attr('value', '').siblings('input.text').val('').siblings('img').hide();
    
               }
    
           });
    
           // Clean up empty inputs in the rank output table
    
           $('#question'+q2ID+' .output table tr').each(function(i) {
    
               var nextRow = $(this).next('tr');
    
               if($('input.text', this).val() == '' && $('input.text', nextRow).val() != '') {
    
                   $('input.text', this).val($('input.text', nextRow).val());
    
                   $('input.text', nextRow).val('');
    
                   $('input[id</div>="fvalue_"]', this).attr('value', $('input[id<div class="simplebox">="fvalue_"]', nextRow).attr('value'));
    
                   $('input[id</div>="fvalue_"]', nextRow).attr('value', '');
    
               }
    
           });
    
           // Show the scissors for the last populated rank output row
    
           $('#question'+q2ID+' .output table img[id<div class="simplebox">="cut_"]').hide();
    
           $('#question'+q2ID+' .output table input.text[value!=""]:last').siblings('img[id</div>="cut_"]').show();
    
           // Hide extra rank output rows
    
           var optNum = $('#question'+q1ID+' input.checkbox:checked').length;
    
           $('#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
    
           if(optNum < 2) {
    
               $('#question'+q2ID+'').hide();
    
               $('#question'+q2ID+' input.text').val('');
    
               $('#question'+q2ID+' input[id<div class="simplebox">="fvalue_"]').attr('value', '');
    
           }
    
           else {
    
               $('#question'+q2ID+'').show();
    
           }
    
           // A work around for the built in max-answers function
    
           $('#question'+q2ID+' select.select').attr('disabled', true);
    
           $('#question'+q2ID+' td.output tr:visible').each(function(i) {
    
               if($('input.text', this).val() == '') {
    
                   $('#question'+q2ID+' select.select').attr('disabled', false);
    
               }
    
           });
    
       }
    
       // A listener to work around the built in max-answers function
    
       $('#question'+q2ID+' td.rank').click(function (event) {
    
           $('#question'+q2ID+' select.select').attr('disabled', true);
    
           $('#question'+q2ID+' td.item input.text').each(function(i) {
    
               if ($(this).val() == '') {
    
                   $('#question'+q2ID+' select.select').attr('disabled', false);
    
               }
    
           });
    
       });
    
    }
    

FOR BOTH FILTER AND RANKING QUESTIONS ON SAME PAGE:

  • Create a multiple options question and a ranking 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 "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.
    <script type="text/javascript" charset="utf-8">
    
       $(document).ready(function() {
    
           rankFilter(MM, RR);
    
       });
    
    </script>
    

FOR FILTER AND RANKING QUESTIONS ON SEPARATE PAGES:

  • 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.
    <script type="text/javascript" charset="utf-8">
    
       $(document).ready(function() {
    
           rankFilter(MM, RR, 1);
    
       });
    
    </script>
    

Filter "Array by Column" Question With "Multiple-Options"

Tested with: 1.91, IE 7/8, FireFox 3/4, Safari 5.0, Chrome 10.0

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 demo of both methods here.

Implementation is as follows.

FOR BOTH METHODS:

  • Set up your survey to use JavaScript.
  • Place the following scripts in your template.js file. These functions 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</div>="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 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).
    <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 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.
    <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.

You can view a small demo survey here.

Implementation is as follows:

  1. Visit http://plugins.jquery.com/node/4046, 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. (See documentation here)
  6. 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

We're considering adding this as a built-in feature for version 1.92 (e.g. an advanced question attribute).

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 Expression Manager rand() function. This can be part of a relevance equation to randomly control which questions are shown; or you can create an Equation question type to store the random value, and let that question be part of downstream relevance equations.

This workaround allows you to generate a random number and then populate a hidden question with that number. You can then use this question to control the display of subsequent questions with Conditions. It's useful if you have several sets of questions that you would like to randomly present to participants - see this forum post.

There is a small demo here.

In the demo, in the first group, we generate a random number between 1 and 4 and populate a hidden question with it. Then in the second group we have 4 questions, each showing conditional on the answer of the first question.

Implementation for this demo is as follows:

  1. Turn off $filterxsshtml to allow insertion of JavaScript in the questions. (See documentation here)
  2. Set up the template to use custom onload functions. (See the workaround here)
  3. In the first group add a "Short free text" question (We're going to hide this with JavaScript)
  4. In the source of that question add the following onload function. (See How to use script here)
  5. In the following groups add the questions that are conditional on the answer of the first question.

Onload code:

<script type="text/javascript">

function Custom_On_Load(){

   $(document).ready(function() {

       // Find a random number between 1 and 4

       var randNumber = Math.floor(Math.random()*4 + 1);

       // Populate and hide the hidden question

       $('input#answerSSSSSXGGXQQ').val(randNumber);

       $('#questionQQ').hide();

       /***********************************************************/

       // This just tells the user which number has been selected

       // You can remove this from your onload function

       $('input#setNumber').val(randNumber);

       $('input#setNumber').css({

           'margin-left':'0.5em',

           'width':'1.5em',

           'text-align':'center',

           'font-weight':'bold'

       });

       $('input#setNumber').attr('disabled', true);

       /***********************************************************/

   });

}

</script>

Where SSSSS is the survey ID, GG is the group ID and QQ is the hidden question ID.

Increase or decrease the maximum value of the random number by modifying the first section. For example to get a number between 1 and 10 you would use this:

var randNumber = Math.floor(Math.random()*10 + 1);

The third section of code just populates a placeholder element that I use to tell the participant what's going on. You can remove it from your code.

Minimum number of required answers in an array

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

As of version 1.92 all array style questions have the min_answers and max_answers options, so this script is obsolete.

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.

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.

There is a demo here.

Implementation is as follows:

  1. Turn off $filterxsshtml to allow insertion of JavaScript in the questions. (See documentation here)
  2. Set up the template to use custom onload functions. (See the workaround here)
  3. In the source of the array question add the following onload function. (See How to use script here)
  4. Modify the SETTINGS section of the code as required.

Onload code:

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

// Wait until the document is fully loaded

$(document).ready(function() {

   /********************** SETTINGS **********************/

   // 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<div class="simplebox">="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>

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. 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.

There is a demo [1].

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 [2])
  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);...)

Onload code:

   $(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);

   });

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.

There is a demo here.

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);...)

Onload code:

<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();

           }

       }

       // Call the function with a question ID

       expandingArray(QQ);

   });

</script>

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

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

This workaround allows the answers of Multiple Options & List (radio) questions to be randomized while always keeping as many answers at the end of the list as specified by the var insertitems=n, where n is the number of answers you want left unrandomized. It's useful if you want to randomize, say, the first 4 of your answers but always have the 5th to 7th displayed last as in the image below (assuming you assigned the number 3 to insertitems):

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.

Some conditions on the use of this are:

  • For Select/radio type questions it will only work if the $shownoanswer option is set to 0 in config.php. ("No answer" is removed from the end of questions)
  • 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 Options or List (radio) 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. Set var insertitems=n (n = the number of items you want un-randomized)
  5. Replace "SSSSS", "GG", "QQ" in the function call at the end with the survey ID, group ID and question ID respectively.
  6. If you want to apply it to more questions on the same page simply add more calls with the appropriate IDs. (eg. partRand(11111, 22, 1); partRand(11111, 22, 2); ...)

Onload code:

<script type="text/javascript">

$(document).ready(function() {

       // Function to allow randomization of all answers except the last one in Multiple options and List/radio questions

       function partRand(sID, gID, qID) {

           //var to define how many lists to be added at the end

           var insertitems=3;

           // Find the number of answers

           var ansCount = ''

           lastitems=new Array();

           var liid=new Array();

           var $ul;

           var j=0;

           $( '#question' + qID + ' td.answer li' ).each(function(i) {

                           ansCount = (i + 1);

});

$( '#question' + qID + ' td.answer li' ).each(function(i) {

                           if(i>ansCount-(insertitems+1)){

                             lastitems[j]=$(this).html();

                             liid[j]=$(this).attr("id");

                             j++;

                             $(this).remove();

                            }

});

$( '#question' + qID + ' td.answer li' ).each(function(i) {

// get current ul

$ul = $(this).parent();

// get array of list items in current ul

var $liArr = $ul.children('li');

// sort array of list items in current ul randomly

$liArr.sort(function(a,b){

// Get a random number between 0 and 100

var temp = parseInt( Math.random()*100 );

// Get 1 or 0, whether temp is odd or even

var isOddOrEven = temp%2;

// Get +1 or -1, whether temp greater or smaller than 5

var isPosOrNeg = temp>5 ? 1 : -1;

// Return -1, 0, or +1

return( isOddOrEven*isPosOrNeg );

})

// append list items to ul

.appendTo($ul);

});

$.each(lastitems,function(i){

$( $ul ).append("<li id="+liid[i]+">"+this+"</li>");

});

   }

       // Call the function with the SID, GID and QID

       partRand(SSSSS, GG, QQ);

   });

</script>

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

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

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:

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.

There is a demo here.

Some conditions on the use of this are:

  • For Select/radio type questions it will only work if the $shownoanswer option is set to 0 in config.php. ("No answer" is removed from the end of questions)
  • 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 Options or List (radio) 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 "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. partRand(11111, 22, 1); partRand(11111, 22, 2); ...)

Onload code:

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

   $(document).ready(function() {

       // Function to allow randomization of all answers except the last one in Multiple options and List/radio questions

       function partRand(sID, gID, qID) {

           // Find the number of answers

           var ansCount = ''

           $( 'div#question' + qID + ' td.answer li' ).each(function(i) {

               ansCount = (i + 1);

           });

           // Place the last answer created at the end of the list

           $( 'input#answer' + sID + 'X' + gID + 'X' + qID + ansCount + '' ).parent().appendTo($( '#question' + qID + ' td.answer ul' ));

       }

       // Call the function with the SID, GID and QID

       partRand(SSSSS, GG, QQ);

   });

</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.

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.

There is a demo here.

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 "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); ...)

Onload code:

<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(sID, gID, qID) {

       // Find the number of answers

       var ansCount = ''

       $( 'div#question' + qID + ' select option' ).each(function(i) {

           ansCount = i;

       });

       // Place the last answer created at the end of the list

$( 'option[value="' + ansCount + '"]' ).appendTo($( 'div#question' + qID + ' select' ));

   }

   // Call the function with the SID, GID and QID

   partRandDD(89344, 72, 244);

});

</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); ...)

Onload code:

<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);

   });

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

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</div>="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: 1.87+ (7557), IE 6/7, Firefox 3.5, Safari 3.2

As of Version 1.92, many more question types support default values.

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.

Implementation is as follows:

  1. Set up your survey to use JavaScript.
  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 + ' table.question tbody tr').each(function(i) {

               if ($('input.radio[checked=true]', this).length == 0) {

                   $('input.radio:eq(' + checkedCol + ')', this).attr('checked', true);

               }

           });

       }

       // Call the function with a question ID and column number

       checkedDefault(QQ, CC);

   });

</script>

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

Version 1.92 supports group-level relevance, making this workaround obsolete.

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 here.

Implementation is as follows:

  1. up your survey to use JavaScript.
  1. Set the survey to run in "all in one" mode.
  1. Add your groups and questions.
  1. 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
  1. inserts a link above each one to toggle its display
  1. some styles are added to the clickable links but these could be done in template.css instead
  1. 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 elements before all group wrapper divs

       $('<div class="groupToggler"><span></span></div>').insertBefore('div[id<div class="simplebox">="group-"]');

       // Add some text to the show/hide elements

       $('.groupToggler span').each(function(i) {

           ($( this ).text('Show/Hide Group '+(i+1)+'']];

       });

       // Add some styles to the show/hide elements

       $('.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 elements

       $(".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 element 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</div>="group-"]').show();

   });

</script>

Use jQuery Autocomplete plugin to suggest answers for text inputs

1.90 and previous versions

Tested with: 1.90+, IE 6/7/8, Firefox 3.6, Safari 3.2

This workaround uses the 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.

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.

I've put together a small demonstration here and implementation is as follows.

Both methods:

  • Download the plugin
  • 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
<script type="text/javascript" src="{TEMPLATEURL}jquery.autocomplete.js"></script>

<link rel="stylesheet" type="text/css" href="{TEMPLATEURL}jquery.autocomplete.css" />

Method 1 ("States" question in the demo):

  • 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(states, {

           matchContains: true,

           minChars: 0

       });

   });

</script>

Method 2 ("Countries" question in the demo):

  • Place the CSV file of suggestions in your template directory (you can download my demo CSV here)
  • 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 countriesArr = new Array();

       $.getJSON(url,function(data){

           $(data).each(function(i, item){

               countriesArr.push(item);

           });

           $('#question'+qID+' input.text').autocomplete(countriesArr, {

               matchContains: true,

               minChars: 0

           });

       });

</script>

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 (you can download my demo CSV here)
  • 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>

Use autocomplete to populate several fields

Tested with: 1.91+, IE 7/8, Firefox 3.6, Safari 3.2

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
  • Add the following line to your startpage.pstpl BEFORE the tag for template.js
    <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 = jQuery.csv()(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>
    

Drag and Drop Rankings

Drag and Drop Ranking - Version 1.90 and newer

Tested with: 1.90+ & 1.91RC4, IE 6/7/8, Firefox 3.6, Safari 3.2

This workaround is a modification of the workaround below and also uses JQuery connected sortables. Thanks to the author of that workaround.

Modifications include:

  • 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 demonstrated here.

Implementation is as follows:

  1. Set up your survey to use JavaScript.
  2. Place the large script below in your template.js file.
  3. Place the small script below in the source a ranking question, (this applies the "dragDropRank" function to the question). Replace "QQ" with the ranking 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.
  1. 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.

Code for template.js:

function dragDropRank(qID, choiceText, rankText) {

   if(!choiceText) {

       choiceText = $('#question'+qID+' td.label label').text();

   }

   if(!rankText) {

       rankText = $('#question'+qID+' td.output tr:first td:eq(1)').text();

   }

   //Add a class to the question

   $('#question'+qID+'').addClass('dragDropRanking');

   // Hide the original question in LimeSurvey (so that we can replace with drag and drop)

   $('#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.

function updateDragDropRank(qID)

{

   // Reload the LimeSurvey choices select element

   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);

           //alert (rankees[index].r_value);

           $(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

// or cached due to a page change.  If so, the already-ranked items are loaded into the sortable list

function loadDragDropRank(qID)

{

   var rankees = [];

   // Loop through each item in the built-in LimeSurvey ranking list looking for non-empty values

   $('#question'+qID+' input[name</div>="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

       var liCode = '<li class="ui-state-default" id="choice_' + value.r_value + '">' + value.r_name + '</li>';

       $(liCode).appendTo('#sortable2'+qID+'');

   });

}

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 question ID):

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

   $(document).ready(function() {

       dragDropRank(QQ);

   });

</script>

Code in the source of the question(s) using custom text for the "Choices" and "Rankings" element header labels (replace "QQ" with the ranking question ID):

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

   $(document).ready(function() {

       dragDropRank(QQ, 'Custom Choices Label', 'Custom Rankings Label');

   });

</script>

Styles for template.css:

/* Drag-n-drop ranking styles */

.dragDropTable {

   border: 0 none;

   border-collapse: collapse;

   width: 100%;

}

.dragDropTable td {

   vertical-align: top;

   width: 50%;

   padding-right: 20px;

}

.dragDropTable .dragDropHeader {

   font-weight: bold;

}

.dragDropTable .dragDropChoices,

.dragDropTable .dragDropRanks {

   margin: 5px 0 0 0;

   background: transparent none;

   border: 0 none;

}

.dragDropTable .dragDropChoiceList,

.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,

.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 */

}

Drag and Drop Ranking using Images - Version 1.90 and newer

Tested with: 1.90+ & 1.91RC4, IE 6/7/8, Firefox 3.6, Safari 3.2

This workaround works in conjunction with the above workaround to allow ranking of images.

There is a demonstrated here.

Implementation is as follows:

1) Follow the above workaround to get basic drag-n-drop functionality in your ranking question.

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).

3) Give the images EXACTLY the same IDs as the answer code you would like them to be associated with:

4) Place the first script below in your template.js file.

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).

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.

Code for template.js:

function dragDropRankImages(qID) {

   $('.connectedSortable'+qID+' li').each(function(i) {

       // Remove any text in the sortable choice or rank items

       $(this).text('');

       // Move the images into the appropriate sortable list item

       var liID = $(this).attr('id');

       liIDArray = liID.split('_');

       $('#question'+qID+' img#'+liIDArray[1]+'').appendTo(this);

   });

}

Code in the source of the question(s):

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

   $(document).ready(function() {

       dragDropRankImages(QQ);

   });

</script>

Styles for template.css:

/* Drag-n-drop for ranking images */
#question11 .dragDropTable {

   width: auto;

}
#question11 .dragDropTable .dragDropChoiceList,
#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 {

   background: 0 none;

   border: 0 none;

   height: 50px;

   width: 50px;

}
#question11 .dragDropTable li img {

   margin: 0;

   padding: 0;

   border: 0 none;

}

Drag and Drop Ranking for iPad - Version 1.91 and newer

Tested with: LimeSurvey 1.91, iPad 2

This workaround extends the above workarounds to work with the touch interface on iPads.

1) Follow the "Drag and Drop Ranking - Version 1.90 and newer" workaround above to get basic drag-n-drop functionality in your ranking question.

2) Download the jQuery.ui.touch-punch.js plugin and save it in your template directory.

3) Replace this line in your startpage.pstpl:

<script type="text/javascript" src="{TEMPLATEURL}template.js"></script>

With this:

<script type="text/javascript" src="{TEMPLATEURL}jquery.ui.touch-punch.js"></script>

<script type="text/javascript" src="{TEMPLATEURL}template.js"></script>

Drag and Drop Ranking - Version 1.86

Tested with 1.86 on IE 7,8, FireFox 3, Chrome, and Safari 4

This workaround uses 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.

I used it for a voting application and some of the element names reflect that use.

It looks best when some CSS is added to control the appearance of the boxers around each choice.

   // 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<div class="simplebox">='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.

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

// 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");

   });

}

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: 1.90+, IE 7/8, Firefox 3.6, Safari 3.2

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.

It's demonstrated here.

Implementation is as follows:

  1. Set up your survey to use JavaScript.
  2. Place the following script in the source of the array question or the group description.
  3. Call the script by replacing "QQ" in line 6 with your array question ID (you can call the script for more array questions by copying this line with new IDs).

NOTE: Care should be taken when using this script and conditional questions on the same page.

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

   $(document).ready(function() {

       // Call the exclude function using question ID(s)

       excludeOpt (QQ);

       // A function to make the last option in each array row exclusive

       function excludeOpt (qID) {

           // Add some classes to the checkboxes so we can manipulate them

           $('#question'+qID+' table.question tbody td').addClass('normalOpt');

           $('#question'+qID+' table.question tbody').each(function(i) {

               $('td:last', this).removeClass('normalOpt').addClass('exlusiveOpt')

           });

           // A listener on the checkbox cells

           $('#question'+qID+' table.question tbody td').click(function (event) {

               // Set some vars

               var el = $(this).parent();

               var optLength = $('td', el).length

               // Uncheck the appropriate boxes in a row

               if ($(this).hasClass('normalOpt')) {

                   $('td:last input[type=checkbox]', el).attr('checked', false);

               }

               else {

                   $('td', el).each(function(i) {

                       if (i < (optLength - 1)) {

                           $('input[type=checkbox]', this).attr('checked', false);

                       }

                   });

               }

           });

           // A listener on the checkboxes

           $('#question'+qID+' table.question tbody td input[type=checkbox]').click(function (event) {

               // Set some vars

               var el2 = $(this).parent().parent();

               var optLength = $('td', el2).length

               // Uncheck the appropriate boxes in a row

               if ($(this).parent().hasClass('normalOpt')) {

                   $('td:last input[type=checkbox]', el2).attr('checked', false);

               }

               else {

                   $('td', el2).each(function(i) {

                       if (i < (optLength - 1)) {

                           $('input[type=checkbox]', this).attr('checked', false);

                       }

                   });

               }

           });

       }

   });

</script>

Load SVG Code From Drawing Tool

Tested with: 1.90+, IE 7/8, Firefox 3.6, Safari 3.2

This workaround uses JavaScript/jQuery to load a drawing tool and store the results in a huge-text question.

The drawing tool is supplied by mainada.net.

Implementation is as follows:

  1. Go to [www.mainada.net/inputdraw/downloads|www.mainada.net/inputdraw/downloads], download the latest package and install all files in the root of your LS installation (you only really need inputdraw.js, inputdraw.non-commercial.v1.5.swf and swfobject.js but the others are cool to play with).
  2. Add the following to the <head> element of your template startpage.pstpl file to pull in the scripts:

+~hs<span style="color:hshs<span style="color:hs~<script src="swfobject.js" type="text/javascript"></script>

+~hshs<span style="color:hshs~<script src="inputdraw.js" type="text/javascript"></script>

  1. Create a huge-text question and add the following script to the source of that question.

The script will">

  • Automatically find the ID of the first huge-text question on the page
  • Insert the drawing element into the huge-text question
  • Hide the textarea of that question
  • Populate it with the SVG code generated by the drawing element.
<script type="text/javascript" charset="utf-8">

   $(document).ready(function() {

       // Find the survey, group and question IDs

       if($( 'input#fieldnames' ).length != 0) {

           var fieldNames = $('input#fieldnames').attr('value');

           var tmp = fieldNames.split('X');

           var sID = tmp[0];

           var gID = tmp[1];

           var qIDStr = $('div.text-huge:eq(0)').attr('id');

           var tmp2 = qIDStr.split('question');

           var qID = tmp2[1];

       }

       var textAreaID = 'answer'+sID+'X'+gID+'X'+qID;

       // Hide the textarea

   $('#'+textAreaID+'').hide();

       // Insert an element for the drawing tool

   $('<div id="place"></div>').insertBefore($('#'+textAreaID+''));

       // Load the drawing tool

   var draw = new InputDraw('inputdraw.non-commercial.v1.5.swf', 'place', {

           id:textAreaID,

           width:'350',

           height:'100',

           stroke_style:'2 1 rgb(255,0,0)',

           animation:60,

           color:'#CCCCCC' ,

           tools_color:'0xd6c004',

           clean:'clear.png',

           undo:'undo.png',

           disable:'size opacity color' // Get rid of most of the tools

       });

   });

</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 using javascript.

Tested with: 1.91+, Firefox 5.0

This javascript inserts a link to opt out of the survey above the progress bar.

First see the section above entitled: "How to use Script (eg. JavaScript etc.) in LimeSurvey?".

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 [3] 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.
Time 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>