You are on page 1of 288

LimeSurvey Manual

Navigation
o Main page
o Recent changes
o Random page
o Help

Search

o
What links here
o
Related changes
o
Special pages
o
Printable version
o
Permanent link
o
Page information
o
Recent changes
o
Help

o
English
o
Log in
o Register
Actions

Workarounds: Manipulating a survey at runtime using


Javascript

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
Workarounds: Survey behaviour
Workarounds: Question design, layout and templating

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.

Contents

[hide]
1 How to add a javascript workaround solution to this Wiki page?
2 How to use Script (eg. JavaScript etc.) in LimeSurvey
o 2.1 Usage of brackets ({ and })
o 2.2 Examples of usage of brackets
3 Sum up input values using Javascript
4 Multiple Question Validation
5 Alternate exit
6 Custom onload function
o 6.1 General
o 6.2 Advanced usage
o 6.3 Language-specific Javascript code
7 Custom onload function in multi-language survey
8 Skipping the welcome page
9 Filter answers of a question with the answer of another
o 9.1 Method 1
o 9.2 Method 2
10 Filter answers of a multiple numeric type question
11 Use an answer to prefill another question (default value)
12 Dynamic Sum
13 Focus on the first Input field
14 Answer questions by keypress
15 Math notation in LimeSurvey
16 Reverse array_filter (for pre-1.90)
17 Filter Ranking Question With Multiple-Options (pre 1.92)
18 Filter "Array by Column" Question With "Multiple-Options"
19 Auto-tabbing between text input fields
o 19.1 Maximum characters (maximum_chars)
20 Custom response listeners
21 Text input masks
22 Text input masks - second method
23 Select a random response to a "Multiple options" question for later use
24 Display secondary options in a "Multiple options" question
25 Minimum number of required answers in an array (pre 1.92)
26 Minimum elapsed time before moving forward in survey
27 Minimum number of characters in Long free text or Huge free text questions
28 Variable Length Array (Multi Flexible Text) question
29 Expandable Array
30 Partially Randomized Answers - Array questions
31 Partially Randomized Answers - Multiple Options & List (radio) questions
32 Partially Randomized Answers - Multiple Options & List (radio) questions (Enhanced)
33 Partially Randomized Answers - List (dropdown) questions
34 Partially Randomized Answers - Multiple numerical input
35 Making one (or more) rows of an array mandatory
36 Randomly displaying one of a few YouTube videos, and recording which was displayed
37 Randomly displaying one of a few pictures, and recording which was displayed
38 Multiple numerical input with max_num_value defined in token (personalized limit)
39 Default values in array questions
40 Default Values In One Scale Of A Dual-scale Array Question
41 Record group view time
42 Toggle visibility of groups
43 Use jQuery Autocomplete plugin to suggest answers for text inputs
o 43.1 1.90 and previous versions
o 43.2 1.91 and later versions
o 43.3 Update for LimeSurvey 2.05+
o 43.4 Use autocomplete to populate several fields
44 Drag and Drop Rankings
o 44.1 Drag and Drop Ranking - Version 1.90 to 1.92
o 44.2 Drag and Drop Ranking using Images - Version 1.90 to 1.92
o 44.3 Drag and Drop Ranking for iPad - Version 1.91 and newer
o 44.4 Drag and Drop Ranking - Version 1.86
45 Create MaxDiff question type
46 In a checkbox matrix, have the 'none' option in a row disable all other columns in that row
47 Last Option In Array (Numbers) (Checkboxes) Row Excludes All Others
o 47.1 Implementation LS Version 2.06:
o 47.2 Implementation LS Version 2.54:
48 Add Navigation Buttons (Next and Previous) To Top Of Page
49 How to add an opt out link to your survey
50 Anonymously track respondents answers across multiple surveys
51 Check validity of international phone number in subquestion
52 Check validity of email address in subquestion
53 Hiding the help-text when it's empty
54 Disable or hide selected fields in a matrix question
55 Add prefix or suffix to question type "Array (Texts)"
56 Time question (Date question-like)
57 Calculating date difference
58 How to set minDate/maxDate based on other Datequestions
59 Remove Question Help if empty
60 Implicit Association Test (IAT)
o 60.1 Version 2.06
o 60.2 Version 2.50
61 Make multiple choice easier
62 Prevent the survey from being submitted when users press enter
63 Card Sorting
64 Text Input (e.g. "short free text", "huge free text"): Force UpperCase + onBlur Trim
65 Convert arrays to dropdowns on smaller screens
66 Van Westendorp Pricing Sliders
o 66.1 Version 2.06
o 66.2 Version 2.5x

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>
Usage of brackets ({ and })

Caution when using brackets ({ and }) in scripts : Expression manager uses brackets ({ and }) to enclose
expressions. If you have to use brackets in your JavaScript, you must add a space or line feed after the opening bracket
({) and before the closing bracket (})

Examples of usage of brackets


Expression Manager will try to parse this JavaScript:

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


$(function() {console.log('broken javascript');});
</script>
Adding line feed prevents Expression Manager from parsing

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


$(function() {
console.log('Adding a line feed for javascript');
});
</script>
Expression Manager will try to parse this JavaScript:

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


if(myvar==1) {console.log('broken javascript');};
</script>
Adding spaces inside the brackets prevents Expression Manager from parsing
<script type="text/javascript" charset="utf-8">
if(myvar==1) { console.log('Adding a space for javascript'); };
</script>

Sum up input values using Javascript


Tested with:

As of version 1.92, this is easier to do using the sum() function in Expression Manager. See this HowTo example.

Starting in Version 1.92, you also do no longer need to use SGQA (e.g. 45251X1X5) identifiers. Instead the code {SGQ} will
automatically replaced by the current questions SGQA-codes.
This is from a survey that is a invitation to a longer party, when the user clicks to the next page, the calculated results will
be shown.
If you wonder what the {INSERTANS:1X6X12} stuff is, look at SGQA identifier and Using information from previous
answers.

<div style="text-align: left">These are your answers:<br />

<u></u><u></u><u></u><u></u><u>=

<p><script LANGUAGE="JavaScript">

partyprice = 3;

beadclothprice = 3;
breakfastprice = 7;

lunchprice = 8;

dinnerprice = 10;

foodtotal = 0;

var isreallytrue = "Yes";

var noanswer = "No answer";

document.write( "Can you come? = {INSERTANS:45251X1X1}<BR>" );

if ("{INSERTANS:45251X1X1}" == isreallytrue )

if ("{INSERTANS:45251X1X3}"== noanswer) { answer = 0; }

else { answer = "{INSERTANS:45251X1X3}"; }

partypeople = parseInt(answer);
partytotal = partyprice * partypeople;

document.write( "It's {INSERTANS:45251X1X3} persons who will come = " + partytotal +


"&euro;<BR>" );

if ("{INSERTANS:45251X1X4}"== noanswer) { answer = 0; }

else { answer = "{INSERTANS:45251X1X4}";}

beadcloths = parseInt(answer);

beadclothtotal = beadclothprice * beadcloths;

document.write( "You want to borrow {INSERTANS:45251X1X4} beadcloths = " + beadclothtotal +


"&euro;<BR>" );

document.write( "Will you be there thursday? = {INSERTANS:45251X1X5}<BR>" );

document.write( "On friday you will have these meals: <BR>" );

if ("{INSERTANS:45251X1X71}"== noanswer) { answer = 0; }

else { answer = "{INSERTANS:45251X1X71}";}


breakfastfriday = parseInt(answer) * breakfastprice;

document.write( "'''_ " + answer + " breakfeasts = " + breakfastfriday + "&euro;<BR>" );

if ("{INSERTANS:45251X1X72}"== noanswer) { answer = 0; }

else { answer = "{INSERTANS:45251X1X72}";}

lunchfriday = parseInt(answer) * lunchprice ;

document.write( "'''_ " + answer + " lunches = " + lunchfriday + "&euro;<BR>" );

if ("{INSERTANS:45251X1X73}"== noanswer) { answer = 0; }

else { answer = "{INSERTANS:45251X1X73}";}

dinnerfriday = parseInt(answer)* dinnerprice ;

document.write( "'''_ " + answer + " dinners = " + dinnerfriday + "&euro;<BR>" );

foodtotal = breakfastfriday + lunchfriday + dinnerfriday;

document.write( "Will you be there saturday? = {INSERTANS:45251X1X8}<BR>" );


if ("{INSERTANS:45251X1X8}" == isreallytrue )

document.write( "On saturday you will have these meals: <BR>" );

if ("{INSERTANS:45251X1X91}"== noanswer) { answer = 0; }

else { answer = "{INSERTANS:45251X1X91}"; }

breakfastsaturday = parseInt(answer) * breakfastprice ;

document.write("'''_ " + answer + " breakfeasts = " + breakfastsaturday + "&euro;<BR>" );

if ("{INSERTANS:45251X1X92}"== noanswer) { answer = 0; }

else { answer = "{INSERTANS:45251X1X92}"; }

lunchsaturday = parseInt(answer) * lunchprice ;

document.write( "'''_ " + answer + " lunches = " + lunchsaturday + "&euro;<BR>" );

if ("{INSERTANS:45251X1X93}"== noanswer) { answer = 0; }


else { answer = "{INSERTANS:45251X1X93}"; }

dinnersaturday = parseInt(answer) * dinnerprice ;

document.write( "'''_ " + answer + " dinners = " + dinnersaturday + "&euro;<BR>" );

foodtotal = foodtotal + breakfastsaturday + lunchsaturday + dinnersaturday;

document.write( "Will you be there sunday? = {INSERTANS:45251X1X10}<BR>" );

if ("{INSERTANS:45251X1X10}" == isreallytrue )

document.write( "On sunday you will have these meals: <br>" );

if ("{INSERTANS:45251X1X111}"== noanswer) { answer = 0; }

else { answer = "{INSERTANS:45251X1X111}"; }

breakfastsunday = parseInt(answer) * breakfastprice ;


document.write( "'''_ " + answer + " breakfeasts = " + breakfastsunday + "&euro;<BR>" );

if ("{INSERTANS:45251X1X112}"== noanswer) { answer = 0; }

else { answer = "{INSERTANS:45251X1X112}"; }

lunchsunday = parseInt(answer) * lunchprice ;

document.write( "'''_ " + answer + " lunches = " + lunchsunday + "&euro;<BR>" );

if ("{INSERTANS:45251X1X113}"== noanswer) { answer = 0; }

else { answer = "{INSERTANS:45251X1X113}"; }

dinnersunday = parseInt(answer) * dinnerprice ;

document.write( "'''_ " + answer + " dinners = " + dinnersunday + "&euro;<p>" );

foodtotal = foodtotal + breakfastsunday + lunchsunday + dinnersunday;

total = partytotal + beadclothtotal + foodtotal;


document.write( "<h3>In total it will cost you " + total + "&euro;</h3><BR>

Put that sum in this bank account 1234 5678 9012" );

</SCRIPT></p>

</div>

Multiple Question Validation


Tested with:

As of version 1.92, this is easier to do using the min/max/equals_num_value attributes (to validate sums), or the
em_validation_q function, which lets you refer to other variables when you validate the current question 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 SGQA Identifiers; this should work on almost any version of LimeSurvey.
Is easy to explain with an example, we need to do a survey of the percentage of males and females and we want to be
sure that the sum is 100, to do this follow this steps (the necessary data that is not indicated in these instructions are not
important i.e. question codes, etc.):
1. Create a new survey of type "group by group", set the "Show |<< Prev| button" to yes and note the SID.
2. Add a new group called "Group 1" and note the GID.
3. Add a new question "Percent of males" of type "numeric", make it mandatory and note the QID (we will refer to this
as QID1).
4. Add a new question "Percent of females" of type "numeric", make it mandatory and note the QID (we will refer to
this as QID2).
5. Add a new group called "Group 2"
6. Add a new non mandatory question, here is the trick the question text has to be (be sure to replace the SID, GID,
QID1 and QID2
<script>

function validation()

if [[{INSERTANS:SIDXGIDXQID1}+{INSERTANS:SIDXGIDXQID2}) != 100)

alert("Your responses don't sum 100! Check them");

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

document.limesurvey.submit();

else

document.limesurvey.move.value = 'movelast';
document.limesurvey.submit();

setTimeout("validation()",250);

</script>
But this will show shortly the question and will jump to the end page, a little weird for the survey taker but works.
This is a proof of concept, and may have some problems, please if you notice something or improve it send me an e-mail
to leochaton-limesurvey at yahoo dot com.
This will work for 2 questions that you want to be the same. Works well for email and confirm email questions

function validation() {

if ("{INSERTANS:SIDXGIDXQID}" != "{INSERTANS:SIDXGIDXQID}") {

alert("Your responses don't match Check them"); document.limesurvey.move.value = 'moveprev';

document.limesurvey.submit();

}
}

setTimeout("validation()",250);

Alternate exit
Tested with:
Using the same idea of the previous post, you can implement an alternate exit of your survey.
This script should be in the first question to be displayed in a next page after the question that should trigger the
alternate exit. In this example the question that trigger the alternate exit is a Yes/No question, if the user chooses no, he is
redirected to another page.

<script>

if ("{INSERTANS:SIDXGIDXQID}" == "No")

window.location="http://www.limesurvey.org";

</script>

Custom onload function


Tested with: 'All browsers', LimeSurvey 1.70 and up

General
This simple workaround describes how to add a custom Javascript code that is triggered right after the page is loaded.
It may have numerous applications, especially when you deal with a custom JS/HTML code. It may be used to hide form
elements (<input> tags), make them read-only, display alert messages, etc.
To run any Javascript code right after page load use the Query ready() function. It will be executed when the page is being
loaded. For example when you add the following code to one of your question definitions...:

<script>

jQuery(document).ready(

function(){

alert('onload alert!');

);

</script>
...it will display a pop-up message immediately after the page has been loaded.
Some of the applications of this trick I've explored are:
a) Hiding the form elements by calling in your Custom_On_Load:

document.getElementById("answer73166X16X54").style.display='none';
Note: As of svn build 9755, you can now dynamically reference the questions input fields by using the {SGQ} keyword.
You can rewrite the previous example like this:

document.getElementById("answer{SGQ}").style.display='none';
this makes it a little easier when exporting surveys, so you don't have to worry about manually entering each questions
SurveyGroupQuestion ID.
b) Making the elements read only:

document.getElementById("answer73166X16X54").readOnly=true;

where the "answer73166X16X54" structure has been described in one of other workarounds above.
c) For Hiding by a lot of elements by calling in your Customer_On_Load
jQuery searchs in html document for input-fields with radio type, if his answer text empty then do the display css set to
none.

jQuery(document).ready(

function() {

jQuery('input:radio').each(

function() {

var s = jQuery(this);

var c = s.parent().children('.answertext');

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


var a = jQuery(c[i]]);

if(this.id </u> a.attr('for') && a.text() == '') {

s.css({ 'display' : 'none' });

a.css({ 'display' : 'none' });

break;

);

Advanced usage
If you want to place global Javascript code using the above jQuery(document).ready function then the best place to do this
is the template javascript file template.js. You can edit this file using the template editor. Please be aware that placing it in
the template javascript file will make the function available to all surveys using that template. If needed just create a new
template copy for your survey.
Alternatively you can place the jQuery(document).ready function in the group description to make a Javascript function
only available to a certain question group.

Language-specific Javascript code


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

jQuery(document).ready(

function(){

languagecode=$('html').attr('lang');

alert('This is the current language code: '+languagecode);

);
For an English survey the above code would show a messagebox with "This is the current language code: en"

Custom onload function in multi-language survey


Tested with: 1.85+ (7253), IE 6/7, FireFox 3.0, Safari 3.2,
To use custom onload functions, as above, in multi-language surveys you must define the functions in the question source
forall 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^="answer"] option').each(function(i){

if($(this).attr('value') && $(this).attr('value').substr(0, 1).toUpperCase() !=


countryCode) {
$(this).remove();
}
});
});

</script>
And the following code in the source of question 3 (City):

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

$(document).ready(function() {

// Get the stateCode (two first characters of the state)

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

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

$('select[id^="answer"] option').each(function(i){
if($(this).attr('value') && $(this).attr('value').substr(0, 2).toUpperCase() != stateCode)
{

$(this).remove();

});

});

</script>
Each question must be in different pages of survey to work, i.e. in a question by question survey, or in diferent groups in a
group by group survey.
Note that if you choose a two characters answer option code in the State question, i.e. a XYY answer option code, you
must change the substr(0, 2) part of code in question 3 to substr(0, 3).

Filter answers of a multiple numeric type question


Tested with: 1.90+

As of version 1.92, this is easier to do using the the array_filter attribute, or relevance equations in general. In 1.92,
array_filter works with all questions that have 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 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 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
inSessionsNotAttended 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 asSessionsAttended is answered
Conditions are used to ensure that RateSessionsAttended only appears if some sessions are selected
andReasonNotAttended only appears if no sessions are selected

Filter Ranking Question With Multiple-Options (pre 1.92)


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^="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^="fvalue_"]

[value="'+ansCode+'"]').length == 0) {

$('<option value="'+ansCode+'">'+ansTxt+'</option>').appendTo('#question'+q2ID+'

select.select');

// If option is unchecked...

else if($(this).attr('checked') == false) {

// Remove it from the rank choices

$('#question'+q2ID+' select.select option[value="'+ansCode+'"]').remove();

// Remove it from the rank output and reset hidden values

$('#question'+q2ID+' .output input[id^="fvalue_"][value="'+ansCode+'"]').attr('value',

'').siblings('input.text').val('').siblings('img').hide();

});

// Clean up empty inputs in the rank output table

$('#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^="fvalue_"]', this).attr('value', $('input[id^="fvalue_"]',

nextRow).attr('value'));

$('input[id^="fvalue_"]', nextRow).attr('value', '');

});

// Show the scissors for the last populated rank output row

$('#question'+q2ID+' .output table img[id^="cut_"]').hide();


$('#question'+q2ID+' .output table input.text[value!=""]:last').siblings('img[id^="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^="fvalue_"]').attr('value', '');

else {

$('#question'+q2ID+'').show();

// A workaround 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"
As of version 2.06 this workaround is not required. It can be handled with sub-question relevance.
Tested with: 2.05
This workaround applies a filter to an "Array by Column" question based on the checked boxes of a previous multiple-
options question. There are two methods - one for filter and array questions being on the same page and a slightly
different one for the filter question being on a page before the array question.
Implementation is as follows.
FOR BOTH METHODS:
Set up your survey to use JavaScript.
If using LimeSurvey version 2.05 or newer, place the following functions in your template.js file. These will be
accessed by either method.
function filterArrByCol(qMultiOpt, qArray, prevPage) {

// If filter question is on a previous page, hide Q1 and check all "visible" boxes

if(prevPage == 1) {

$('#question'+qMultiOpt+' li[id^="javatbd"]:visible input.checkbox').attr('checked', true);

$('#question'+qMultiOpt+'').hide();

// Assign classes to the answer cells

$('#question'+qArray+' td.answer-item').each(function(i){

var classArr = $(this).attr('class').split('answer_cell_00');

classArr = classArr[1].split(' ');

var ansCode = classArr[0];

$(this).addClass('ans-'+ansCode+' filtered');

});

// Assign classes to the answer label cells

$('#question'+qArray+' table.subquestions-list tbody tr:eq(0) td.answer-item').each(function(i){

var classArr2 = $(this).attr('class').split(' ans-');

var ansCode2 = classArr2[1];

$('#question'+qArray+' table.subquestions-list thead tr:eq(0)

th:eq('+i+')').addClass('ans-'+ansCode2+'');

});

// Fire the filter function on page load

filterArr(qMultiOpt, qArray);

// Listener on multi-opt checkboxes to fire the filter function

$('#question'+qMultiOpt+' input.checkbox').click(function(){

filterArr(qMultiOpt, qArray);

});

// On submit, clear all hidden radios of array

$('#movenextbtn, #movesubmitbtn').click(function(){

$('#question'+qArray+' td.filtered:hidden').each(function(i){

$('input.radio', this).prop('checked', false);

});

return true;

});

function filterArr(qMultiOpt, qArray) {

if($('#question'+qMultiOpt+' input.checkbox:checked').length < 1) {

// Hide the array if no multi-opt options are checked

$('#question'+qArray+'').hide();

else {
$('#question'+qArray+'').show();

// Hide all columns of array

$('#question'+qArray+' table.subquestions-list tbody td.answer-item, #question'+qArray+'

table.question thead th').hide();

// Loop through multi-opt checkboxes and, if checked, show corresponding column of array

$('#question'+qMultiOpt+' input.checkbox').each(function(i){

if($(this).prop('checked') == true) {

var classArr3 = $(this).attr('id').split('X'+qMultiOpt);

var ansCode3 = classArr3[1];

$('#question'+qArray+' .ans-'+ansCode3+'').show();

});

If using LimeSurvey version 2.0 or older, place the following functions in your template.js file. These will be
accessed by either method.
$(document).ready(function() {

function filterArrByCol(qMultiOpt, qArray, prevPage) {

// If filter question is on a previous page, hide Q1 and check all "visible" boxes

if(prevPage == 1) {
$('#question'+qMultiOpt+' li[id^="javatbd"]:visible input.checkbox').attr('checked', true);

$('#question'+qMultiOpt+'').hide();

// Assign classes to the answer cells

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

var classArr = $(this).attr('class').split('answer_cell_00');

var ansCode = classArr[1];

$(this).addClass('ans-'+ansCode+' filtered');

});

// Assign classes to the answer label cells

$('#question'+qArray+' table.question tbody tr:eq(0) td').each(function(i){

var classArr2 = $(this).attr('class').split(' ans-');

var ansCode2 = classArr2[1];

$('#question'+qArray+' table.question thead tr:eq(0)

th:eq('+i+')').addClass('ans-'+ansCode2+'');

});

// Fire the filter function on page load

filterArr(qMultiOpt, qArray);

// Listener on multi-opt checkboxes to fire the filter function

$('#question'+qMultiOpt+' input.checkbox').click(function(){
filterArr(qMultiOpt, qArray);

});

// On submit, clear all hidden radios of array

$('#movenextbtn, #movesubmitbtn').click(function(){

$('#question'+qArray+' td.filtered:hidden').each(function(i){

$('input.radio', this).attr('checked', false);

});

return true;

});

function filterArr(qMultiOpt, qArray) {

if($('#question'+qMultiOpt+' input.checkbox:checked').length < 1) {

// Hide the array if no multi-opt options are checked

$('#question'+qArray+'').hide();

else {

$('#question'+qArray+'').show();

// Hide all columns of array

$('#question'+qArray+' table.question tbody td, #question'+qArray+' table.question thead

th').hide();

// Loop through multi-opt checkboxes and, if checked, show corresponding column of array

$('#question'+qMultiOpt+' input.checkbox').each(function(i){

if($(this).attr('checked') == true) {

var classArr3 = $(this).attr('id').split('X'+qMultiOpt);

var ansCode3 = classArr3[1];

$('#question'+qArray+' .ans-'+ansCode3+'').show();

});

});

FOR BOTH FILTER AND ARRAY QUESTIONS ON SAME PAGE:


Create a multiple options question and an array-by-column question on the same page (in the same group).
Both questions MUST have identical 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/autotabs, download the plugin script and save it as jquery.autotab-1.1b.js in the
template folder.
2. Link to the script by placing <script type="text/javascript" src="{TEMPLATEURL}jquery.autotab-
1.1b.js"></script> within the<head> tag of startpage.pstpl.
3. Set up your survey to use JavaScript.
4. Create the text based questions that auto-tabbing is to be applied to. (All must be in the same group if you're using
group by group presentation)
5. Define a Maximum characters attribute for all questions that the autotab is applied to.

Maximum characters (maximum_chars)


Description
This allows you to set the maximum number of characters that can be entered for a text based question. Entering a value
of, say, 20 will mean that the participant cannot enter any more than 20 characters.
Valid values
Any integer value above 0

1. In the source of the first question of the group that contains the auto-tabbed questions, add the following code.
<script type="text/javascript" charset="utf-8">

$(document).ready(function() {

$('#answerSSSSSXGGXAA').focus(); //initially focus on the first input

$('#answerSSSSSXGGXAA').autotab({ target: 'answerSSSSSXGGXBB' });

$('#answerSSSSSXGGXBB').autotab({ previous: 'answerSSSSSXGGXAA', target:


'answerSSSSSXGGXCC' });

$('#answerSSSSSXGGXCC').autotab({ previous: 'answerSSSSSXGGXBB', target:


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

});

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

The target parameter defines where the next input is and the previous parameter defines (you guessed it) the previous
input.
This plugin also has limited capability of formatting the input text - see comments in the plugin script.
Custom response listeners
Tested with: 1.85+ (7253), IE 6/7, FireFox 3.0, Safari 3.2, Chrome 2.0
Listeners can be used to perform functions when a change in an input is detected. There are many uses such as
the Reverse array_filter but a simple one would be to warn a respondent if they enter a value that may be too high.
We can use the jQuery change( ) event to fire a function whenever there is a change to the input. The function will look for
an input value higher than 10 and, if found, will warn the respondent.
1. Set up your survey to use JavaScript.
2. Place the script below in the source of one of the questions on the page where:

o 11111 is the survey ID
o 22 is the group ID
o 33 is the question ID
<script type="text/javascript" charset="utf-8">

$(document).ready(function() {

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

if ( $('#answer11111X22X33').val() > 10 ) {
alert ("That's an awful lot. Are you sure?");
}
});
});
</script>
Text input masks
Tested with: 1.90+, IE 7, FireFox 3.0, Safari 3.2, Chrome 2.0
There may be instances where you would like to place a mask on a text input - for example only allowing respondents to
enter the correct characters for a North American phone number ((###) ###-####) or a Canadian postal code (A1A
1A1) The jQuery meioMask plugin by fabiomcosta can be used to do this.
The plugin has several pre-defined masks and supports custom masks. It also allows for pasting of input, allows default
values and can auto-focus the next form element when the current input is completely filled.
Seehttp://www.meiocodigo.com/projects/meiomask/ for more details on features and options.
There is a small demo here.
Implementation for this demo was as follows:
1. Visit http://www.meiocodigo.com/projects/meiomask/, download the latest plugin script and save it as
jquery.meiomask.js in the template folder.
2. Link to the script by placing <script type="text/javascript" src="{TEMPLATEURL}jquery.meiomask.js" charset="utf-
8"></script> within the <head> tag of startpage.pstpl.
3. Turn off $filterxsshtml to allow insertion of JavaScript in the questions. (See documentation here)
4. Create the text input questions that the masks are to be applied to
5. In the source of the help section of the first question of the group that contains the masked inputs, add the
following code. (See How to use script here)

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

$(document).ready(function() {

// Define custom masks


$.mask.masks = $.extend($.mask.masks,{
customMask1:{ mask: 'a9a 9a9' }, // Canadian postal code
// Maximum input of 9,999.99, reverse input, default value of 0.00
customMask2:{ mask: '99.999,9', type : 'reverse', defaultValue : '000' }
});

// Set the 'alt' attributes of the inputs


$('#answer111111X22XAA').attr('alt', 'phone-us'); // a pre-defined mask

$('#answer111111X22XBB').attr('alt', 'customMask1'); // a custom mask

$('#answer111111X22XCC').attr('alt', 'customMask2'); // a custom mask

$('#answer111111X22XDD').attr('alt', 'cc'); // a pre-defined mask for credit card

// Tell the plugin to apply masks to these inputs


$('input:#answer111111X22XAA').setMask();
$('input:#answer111111X22XBB').setMask();
$('input:#answer111111X22XCC').setMask();
$('input:#answer111111X22XDD').setMask();
});
</script>

The first section defines two custom masks to be used - one for Canadian postal code with a format of A#A #A# and a
second for a maximum amount of 9999.99 with reverse input (useful for cost inputs) and a default value of 0.00.
The next section sets the "alt" attributes for the text inputs. This will tell meioMask which mask we will be applying to
each input. By default meioMask looks at the alt attribute to find which mask to apply however this can be changed using
the mask options.
In the final section we apply the masks to the inputs.
This example has 4 inputs with masks on them - all instances of the following must be replaced to be compatible with
your survey (See image below):
11111 -> Survey ID
22 -> Group ID
AA -> First question
BB -> Second question
CC -> Third question
DD -> Fourth question

Text input masks - second method


Tested with: 1.91RC6+, FireFox 4.0 Tested with: 2.05, Chrome 40.0.2214.115 m (64-bit)
There may be instances where you would like to place a mask on a text input - for example only allowing respondents to
enter the correct characters for a North American phone number ((###) ###-####) or a US social security number
(###-##-####). The jQuery meioMask plugin by fabiomcosta is no longer supported but an alternative exists. The
DigitalBush Masked Input Plugin is updated and simple to implement in LimeSurvey.
See http://digitalbush.com/projects/masked-input-plugin/ for more details on features and options.
Implementation as follows:
1. Visit http://digitalbush.com/projects/masked-input-plugin/, download the latest plugin script and save it as
jquery.maskedinput.js in the template folder.
2. Link to the script by placing <script type="text/javascript" src="{TEMPLATEURL}jquery.maskedinput.js"
charset="utf-8"></script> within the <head> tag of startpage.pstpl.
3. Turn off $filterxsshtml to allow insertion of JavaScript in the questions. (See documentation here)
4. Create the text input questions that the masks are to be applied to
5. In the source of the help section of the first question of the group that contains the masked inputs, add the
following code. (See How to use script here)

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


$(document).ready(function($) {
$("#answer55431X1X5").mask("999-99-9999",{ placeholder:"#" });
});
</script>

Replace the "#answer55431X1X5" with your survey ID, group ID, and question ID. You can change the mask format and
change or eliminate the placeholder if you wish. If you have multiple questions in the same group, simply copy and paste
the line that begins with "$" and change the code to what is needed for your application.

Select a random response to a "Multiple options"


question for later use
Tested with: LimeSurvey versions 2.06 and 2.5, IE 7-11, Firefox, Chrome
This workaround allows you to randomly select one of the checked boxes in a "Multiple options" question and store it's
label for later use in the survey. A possible use-case would be to have a question asking what products were purchased
and then randomly selecting one of the products to ask further questions about.
The workaround puts a listener on the checkboxes that loops through all of the checked boxes and adds their labels to an
array. A random item is pulled from the array and loaded into the hidden question. This hidden question can then be used
in Expression Manager equations, relevance or conditions.

DOWNLOAD:
- Demo survey for LS 2.06
- Demo survey for LS 2.5

IMPLEMENTATION (LimeSurvey version 2.06)


1. Set up your survey to use JavaScript.
2. Create a "Multiple options" question
3. Immediately following that question, create a short-text question to store the label of the checked box in (we'll hide
this with JavaScript)
4. Place the script below in the source of the multiple options question.
5. <script type="text/javascript" charset="utf-8">

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

7.

8. // Identify the questions

9. var thisQuestion = $('#question{QID}');

10. var qHidden = thisQuestion.nextAll('.text-short:eq(0)');

11. var hiddenInput = $('input.text', qHidden);

12.

13. // Hide qHidden

14. qHidden.hide();

15.

16. // Listener on the checkboxes

17. $('input.checkbox', thisQuestion).on('change', function(e) {

18. handleChecked();

19. });

20.

21. // Listener on the "Other" input

22. $('input.text', thisQuestion).on('keyup change', function(e) {

23. setTimeout(function() {
24. handleChecked();

25. }, 250);

26. });

27.

28. function handleChecked() {

29. // Build an array of checked answers

30. var checkedAnswers = [];

31. $('input.checkbox:checked', thisQuestion).each(function(i) {

32. if($(this).closest('.answer-item').hasClass('other-item')) {

33. checkedAnswers.push($(this).closest('.other-item').find('input.text').val());

34. }

35. else {

36. checkedAnswers.push($(this).nextAll('label:eq(0)').text());

37. }

38. });

39.

40. // Load the hidden question with a random item from the array

41. var checkedLength = checkedAnswers.length;

42. $(hiddenInput).val(checkedAnswers[Math.floor(Math.random()*checkedLength)]);

43.

44. // Fire Expression Manager

45. checkconditions(hiddenInput.value, hiddenInput.name, hiddenInput.type);

46. }

47. });
48. </script>

IMPLEMENTATION (LimeSurvey version 2.5)


1. Follow the first 3 implementation steps above
2. Place the script below in the source of the multiple options question.
3. <script type="text/javascript" charset="utf-8">

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

5.

6. // Identify the questions

7. var thisQuestion = $('#question{QID}');

8. var qHidden = thisQuestion.nextAll('.text-short:eq(0)');

9. var hiddenInput = $('input.text', qHidden);

10.

11. // Hide qHidden

12. qHidden.hide();

13.

14. // Class for "Other

15. $('input.text', thisQuestion).closest('.answer-item').addClass('other-item');

16.

17. // Listener on the checkboxes

18. $('input.checkbox', thisQuestion).on('change', function(e) {

19. handleChecked();

20. });

21.

22. // Listener on the "Other" input


23. $('input.text', thisQuestion).on('keyup change', function(e) {

24. setTimeout(function() {

25. handleChecked();

26. }, 250);

27. });

28.

29. function handleChecked() {

30. // Build an array of checked answers

31. var checkedAnswers = [];

32. $('input.checkbox:checked', thisQuestion).each(function(i) {

33. if($(this).closest('.answer-item').hasClass('other-item')) {

34. checkedAnswers.push($(this).closest('.answer-

item').find('input.text').val());

35. }

36. else {

37. checkedAnswers.push($.trim($(this).nextAll('.label-text:eq(0)').text()));

38. }

39. });

40.

41. // Load the hidden question with a random item from the array

42. var checkedLength = checkedAnswers.length;

43. $(hiddenInput).val(checkedAnswers[Math.floor(Math.random()*checkedLength)]);

44.

45. // Fire Expression Manager


46. checkconditions(hiddenInput.value, hiddenInput.name, hiddenInput.type);

47. }

48. });

49. </script>

Display secondary options in a "Multiple options"


question
Tested with: LimeSurvey versions 2.06 and 2.5, IE 7-11, Firefox, Chrome
This workaround allows you to display secondary options in a "Multiple options" question if a primary option is checked as
in the images below.
Primary option not selected:

Primary option selected:


DOWNLOADS:
- Demo template for LS version 2.06
- Demo survey for LS version 2.06.
- Demo template for LS version 2.5
- Demo survey for LS version 2.5.

IMPLEMENTATION: (LimeSurvey version 2.06)


1. Set up your survey to use JavaScript.
2. Create a "Multiple options" question including both the primary and secondary sub-questions (in the order that you
would like them displayed if all shown).
3. Add the following function to the end of template.js:
4. // A function to handle "secondary" checkboxes

5. function secondaryCheckboxes(qID, primaryPosition, secondaryCount) {

6. // Identify the elements

7. var thisQuestion = $('#question'+qID);

8. var primaryRow = $('li.question-item:eq('+(primaryPosition-1)+')', thisQuestion);

9. var primaryInput = $('input.checkbox', primaryRow);

10. var secondaryRows = primaryRow.nextAll('li.question-item:lt('+(secondaryCount)+')');

11. var secondaryInputs = $('input.checkbox', secondaryRows);

12.

13. // Indent the secondaries

14. secondaryRows.css({ 'margin-left':'2.5em' });

15.

16. // Initial states of the secondary answers

17. if (primaryInput.prop('checked') == false ) {

18. secondaryRows.hide();

19. }

20.

21. // A listener on the primary answer to show or hide secondary answers

22. primaryInput.click(function (event) {

23.

24. // Hide/show the secondary answers accordingly

25. if (!$(this).is(':checked')) {

26. secondaryRows.hide();
27. secondaryInputs.prop('checked', false);

28. secondaryInputs.each(function(i) {

29. checkconditions(this.value, this.name, this.type);

30. });

31. }

32. else {

33. secondaryRows.show();

34. }

35. });

36. }

37. Call the function in the source of the question with the question ID (no editing necessary), the start position of the
primary sub-question and the number of secondaries to follow.
So, in this example, sub-question 1 is a primary followed by 2 secondaries and subquestion 6 is a primary followed
by 3 secondaries.
38. <script type="text/javascript" charset="utf-8">

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

40. // Sub-question 1 is primary followed by 2 secondaries

41. secondaryCheckboxes({QID}, 1, 2);

42. // Sub-question 6 is primary followed by 3 secondaries

43. secondaryCheckboxes({QID}, 6, 3);

44. });

45. </script>

IMPLEMENTATION: (LimeSurvey version 2.5)


1. Follow the first 2 implementation steps above
2. Add the following function to the end of template.js:
3. // A function to handle "secondary" checkboxes

4. function secondaryCheckboxes(qID, primaryPosition, secondaryCount) {

5. // Identify the elements

6. var thisQuestion = $('#question'+qID);

7. $('div.question-item', thisQuestion).parent().addClass('answer-row');

8. var primaryRow = $('div.question-item:eq('+(primaryPosition-1)+')', thisQuestion).closest('.answer-row');

9. var primaryInput = $('input.checkbox', primaryRow);

10. var secondaryRows = primaryRow.nextAll('div.answer-row:lt('+(secondaryCount)+')');

11. var secondaryInputs = $('input.checkbox', secondaryRows);

12.

13. // Indent the secondaries

14. secondaryRows.css({ 'margin-left':'2.5em' });

15.

16. // Initial states of the secondary answers

17. if (primaryInput.prop('checked') == false ) {

18. secondaryRows.hide();

19. }

20.

21. // A listener on the primary answer to show or hide secondary answers

22. primaryInput.click(function (event) {

23.

24. // Hide/show the secondary answers accordingly

25. if (!$(this).is(':checked')) {

26. secondaryRows.hide();
27. secondaryInputs.prop('checked', false);

28. secondaryInputs.each(function(i) {

29. checkconditions(this.value, this.name, this.type);

30. });

31. }

32. else {

33. secondaryRows.show();

34. }

35. });

36. }

37. Call the function in the source of the question with the question ID (no editing necessary), the start position of the
primary sub-question and the number of secondaries to follow.
So, in this example, sub-question 1 is a primary followed by 2 secondaries and subquestion 6 is a primary followed
by 3 secondaries.
38. <script type="text/javascript" charset="utf-8">

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

40. // Sub-question 1 is primary followed by 2 secondaries

41. secondaryCheckboxes({QID}, 1, 2);

42. // Sub-question 6 is primary followed by 3 secondaries

43. secondaryCheckboxes({QID}, 6, 3);

44. });

45. </script>
Minimum number of required answers in an array (pre
1.92)
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.
<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^="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. Set up your survey to use JavaScript.
1. Place the script below in the source of one of the questions on the page, replace "QQ" with the question ID and
"CC" with the minimum number of characters required before advancing is allowed.
<script type="text/javascript" charset="utf-8">

$(document).ready(function() {

minChar(QQ, CC);

function minChar(qID, minChars) {

$('#movenextbtn, #movesubmitbtn').click(function(){

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

alert ('You must enter at least '+minChars+' characters.');


return false;

else {

return true;

});

});

</script>

Variable Length Array (Multi Flexible Text) question


Tested with: 1.90+, IE 6/7/8, FireFox 3.0, Safari 3.2
This workaround allows a respondent to add or remove rows of an Array (Multi Flexible Text) question. It's useful to avoid
the page being cluttered with unnecessary array rows.
A function is placed in the template.js file of your template (use template editor). This function hides all of the array rows
except the first and inserts two elements that when clicked show or hide rows accordingly.
There is a demo here.
It must be noted that you can not make this question Mandatory as the respondent will have no way to complete hidden
fields. If you want the displayed fields to be mandatory you will need to validate them in another function or modify this
function to only allow the addition of rows if all fields in the last row are completed and then disable those fields - that,
however, is a little over the top for this example.
Implementation is as follows:
1. Set up your survey to use JavaScript (See instructions here)
1. Add the following script to your template.js file.
1. Replace "QQ" in the function call at the end with the question ID.
1. Create one or more Array (Multi Flexible Text) questions
1. If you want to apply it to more arrays on the same page simply add more calls with the appropriate question IDs.
(eg. varLengthArray(1); varLengthArray(2);...)

$(document).ready(function() {

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


function varLengthArray(qID) {

if ($('#question'+qID+'').length > 0) {

// The HTML content of the Add/Remove elements - modify as you wish


var addContent = '[+]';
var removeContent = '[-]';

// Create the Add and Remove elements & insert them


var el1 = document.createElement('div');
el1.setAttribute('id','addButton'+qID);
document.body.appendChild(el1);
var el2 = document.createElement('div');
el2.setAttribute('id','removeButton'+qID);
document.body.appendChild(el2);

// Move them to after the array


$( 'div#addButton'+qID ).appendTo($( '#question' + qID + '
table.question' ).parent());
$( 'div#removeButton'+qID ).appendTo($( '#question' + qID + '
table.question' ).parent());

// Insert their HTML


$( 'div#addButton'+qID ).html( addContent );
$( 'div#removeButton'+qID ).html( removeContent );

// Style the elements - you can modify here if you wish


$( 'div#addButton'+qID ).css({
'margin':'10px 0 0 10px',
'padding':'1px',
'text-align':'center',
'font-weight':'bold',
'width':'auto',
'cursor':'pointer',
'float':'left'
});

$( 'div#removeButton'+qID ).css({
'margin':'10px 0 0 10px',
'padding':'1px',
'text-align':'center',
'font-weight':'bold',
'width':'auto',
'cursor':'pointer',
'float':'left'
});

// Initially hide the Remove element


$( 'div#removeButton'+qID ).hide();

// Call the functions below when clicked


$( 'div#addButton'+qID ).click(function (event) {
addRow(qID);
});
$( 'div#removeButton'+qID ).click(function (event) {
removeRow(qID);
});
// Function to add a row, also shows the Remove element and hides the
//Add element if all rows are shown
function addRow(qID) {
var arrayRow = '#question' + qID + ' table.question tbody';
var rowCount = $( arrayRow ).size() - 1;
$( arrayRow + '[name="hidden"]:first' ).attr('name', 'visible').show();
$( 'div#removeButton'+qID ).show();
if ( $( arrayRow + ':eq(' + rowCount + ')' ).attr('name') == 'visible' )
{
$( 'div#addButton'+qID ).hide();
}
}

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

// Just some initialization stuff


var arrayRow = '#question' + qID + ' table.question tbody';
var rowCount = '';

// Initially hide all except first row or any rows with populated inputs
$( arrayRow ).each(function(i) {
if ( i > 0 ) {
// We also need to give the hidden rows a name cause IE doesn't
// recognize jQuery :visible selector consistently
$( this ).attr('name', 'hidden').hide();

$('input[type=text]', this).each(function(i) {
if ($(this).attr('value') != '') {
$(this).parents('tbody:eq(0)').attr('name',
'visible').show();
$( 'div#removeButton'+qID ).show();
}
});
rowCount = i;
}
});
}
}

// Call the function with a question ID


varLengthArray(QQ);

});

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


Tested with: 2.05
This workaround allows the answers of Array questions to be randomized while always keeping one sub-question at the
end of the list.
After the LimeSurvey randomizing has occurred a function in the source of a question moves the sub-question with a
defined code to the bottom of the array.
Implementation is as follows:
1. Set up your survey to use JavaScript
2. Create an Array type question.
3. Set the "Random order" question setting to "Randomize on each page load".
4. Place the following script in the source of the question.
5. Modify the "fixedCode" value as required.

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


$(document).ready(function() {
// The sub-question code to place in the last position
var fixedCode = 'A1';

// Identify this question


var q1ID = {QID};
var thisQuestion = $('#question'+q1ID);

// Move the "fixed" row to the end


$('table.subquestion-list tbody', thisQuestion).append($
('tr[id$="X'+q1ID+fixedCode+'"]'));

// Fix up the array row background colours


$('tr.answers-list', thisQuestion).each(function(i){
$(this).removeClass('array1 array2').addClass('array'+(2-(i%2)));
});

});
</script>
Download sample survey:
Partially_randomized_array.lss

Partially Randomized Answers - Multiple Options & List


(radio) questions
Tested with: 2.05
This workaround allows the answers of Multiple Options & List (radio) questions to be randomized while always keeping
one answer at the end of the list. It's useful if you want to randomize, say, the first 4 of your answers but always have the
fifth displayed last as in the image below:

After the LimeSurvey randomizing has occurred a function in the source of a question moves the answer with the highest
answer code to the end of the list.
Some conditions on the use of this are:
The "Show No answer" survey setting must be set to "No"
You must use sequential numbers, starting at 1 as response codes. (See image below)

Implementation is as follows:
1. Set up your survey to use JavaScript
2. Create a Multiple Options or List (radio) question with sequential response codes.
3. Set the "Random order" question setting to "Randomize on each page load".
4. Place the following script in the source of the question

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

$(document).ready(function() {

// Identify this question


var qID = {QID};

// Find the number of answers


var ansCount = $('#question'+qID+' li.answer-item').length;

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


var answer = $( 'input[id$="X'+qID+ansCount+'"]');
var answerItem = $(answer).closest('li');
var answersList = $(answer).closest('ul');
$(answersList).append(answerItem);

});
</script>
Download sample survey:
Partially_Ranomized_Answers.lss
Partially Randomized Answers - Multiple Options & List
(radio) questions (Enhanced)
Tested with LimeSurvey versions 2.06 and 2.50
This workaround allows the answers of Multiple Options & List (radio) questions to be randomized while always keeping a
specified number of answers fixed at the end of the list. It's useful if you want to randomize, say, the first 4 of your
answers but always have the fifth and sixth displayed last as in the image below:

After the LimeSurvey randomizing has occurred, a function in the source of a question moves the answers with the highest
answer codes to the end of the list.
Some conditions on the use of this are:
The "Show No answer" survey setting must be set to "No"
You must use sequential numbers, starting at 1 as response codes. (See image below)

Implementation for LimeSurvey version 2.06:


1. Set up your survey to use JavaScript
2. Create a Multiple Options or List (radio) question with sequential response codes.
3. Set the "Random order" question setting to "Randomize on each page load".
4. Place the following script in the source of the question.
5. Modify the "fixedAnswers" variable as required.

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

$(document).ready(function() {

// The number of answers to be fixed at the end of the list


var fixedAnswers = 2;

// Identify this question


var qID = {QID};

// Find the number of answers


var ansCount = $('#question'+qID+' li.answer-item').length;

// Place the last n answers created at the end of the list


var fixedIndex = fixedAnswers - 1;
for (var i=0; i<fixedAnswers; i++) {
var answer = $( 'input[id$="X'+qID+(ansCount-fixedIndex)+'"]');
var answerItem = $(answer).closest('li');
var answersList = $(answer).closest('ul');
$(answersList).append(answerItem);
fixedIndex--;
}
});
</script>
Download sample survey for LS 2.06:
Partially_Randomized_Answers_Enhanced.lss

Implementation for LimeSurvey version 2.50:


1. Set up your survey to use JavaScript
2. Create a Multiple Options or List (radio) question with sequential response codes.
3. Set the "Random order" question setting to "Randomize on each page load".
4. Place the following script in the source of the question.
5. Modify the "fixedAnswers" variable as required.

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

$(document).ready(function() {

// The number of answers to be fixed at the end of the list


var fixedAnswers = 2;

// Identify this question


var qID = {QID};

// Find the number of answers


var ansCount = $('#question'+qID+' .answer-item').length;

// Place the last n answers created at the end of the list


var fixedIndex = fixedAnswers - 1;
for (var i=0; i<fixedAnswers; i++) {
var answer = $('input[id^="answer"][id$="X'+qID+(ansCount-fixedIndex)+'"]');
var answerItem = $(answer).closest('.answer-item');
var answersList = $(answer).closest('.answers-list');
if($('#question'+qID).hasClass('multiple-opt')) {
answerItem = $(answer).closest('.answer-item').parent();
answersList = $(answer).closest('.subquestion-list');
}
$(answersList).append(answerItem);
fixedIndex--;
}
});
</script>
Download sample survey for LS 2.50:
Partially_Randomized_Answers_Enhanced_v2.50.zip

Partially Randomized Answers - List (dropdown)


questions
Tested with: 1.90+, IE 6/7, FireFox 3.0, Safari 3.2
This workaround, similar to the one above, allows the answers of List (dropdown) questions to be randomized while always
keeping one answer at the end of the list. It's useful if you want to randomize, say, the first 4 of your answers but always
have the fifth displayed last.
A script is placed in the source of a question. After the randomizing has occurred this script moves the answer with the
highest answer code to the end of the list.
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); ...)
<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); ...)
<script type="text/javascript" charset="utf-8">

$(document).ready(function() {

// Function to allow randomization of all answers except the last one in multiple
numeric input questions
function partRand(sID, gID, qID) {

// Find the number of answers


var ansCount = ''
$( 'div#question' + qID + ' div.answers li' ).each(function(i) {
ansCount = (i - 1);
});

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

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


partRand(SID, GID, QID);

});

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

Making one (or more) rows of an array mandatory

Hint: With Expression manager, you can use Using em_validation_q in array system.
Tested with: 1.90+, IE 6/7, Firefox 3.0, Safari 3.2
This workaround allows you to define one or more rows of an array question as mandatory (instead of all of them which
the "Mandatory" question setting does). It is demonstrated here.
A JavaScript function interrupts the submit function and checks if the inputs in the mandatory row(s) are populated. If not,
an alert is popped up, the offending inputs are turned pink and the submit is aborted. Otherwise the submit goes ahead.
Implementation is as follows:
1. Turn off $filterxsshtml to allow insertion of JavaScript in the questions (see documentation here).
2. Create the array question.
3. In the source of the array question add the following script (see How to use script here).
4. Modify the warningText (line10)as required.
5. Replace "QQ" (lines 13 and 14) with the question ID and add calls as necessary for more rows - the example code
renders rows 1 and 3 mandatory.
<script type="text/javascript" charset="utf-8">

$(document).ready(function () {

// Interrupt the submit function


$('form#limesurvey').submit(function () {

// Override the built-in "disable navigation buttons" feature


$('#moveprevbtn, #movenextbtn, #movesubmitbtn').attr('disabled', '');

var empty = 0;
var warningText = 'Please complete the highlighted inputs.';

// Call the mandatory row function with question IDs and row numbers
mandatoryRow(QQ, 1);
mandatoryRow(QQ, 3);

// A function to render rows of an array mandatory


function mandatoryRow(qID, rowNum) {

$('div#question' + qID + ' table.question tbody[id^="javatbd"]:eq(' +


Number(rowNum - 1) + ') input[type="text"]').each(function (i) {

if ($(this).val() == '') {
$(this).css('background-color', 'pink');
empty = 1;
}
else {
$(this).css('background-color', '#FFFFFF');
}
});
}

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

});

</script>

Randomly displaying one of a few YouTube videos, and


recording which was displayed
Tested in 1.85+
Jason Cleeland has created and documented a method to randomly display one of a number of youtube videos in a single
question, and save which one was displayed.
http://www.aptigence.com.au/home/archives/23-Random-display-of-videos-in-a-question-2-YouTube.html

Randomly displaying one of a few pictures, and


recording which was displayed
Tested in 1.91+
The following steps allow you to randomly display one of a few pictures and record in the dataset which picture was
displayed. We use four pictures in this example, but you can easily adapt these scripts according to your needs.
1. Create a new sub-directory pic in your LimeSurvey folder.
2. Copy four pictures (named 1.jpg, 2.jpg, 3.jpg, and 4.jpg) in this pic sub-directory.
3. Deactivate the Filter HTML for XSS function in LimeSurvey's Global Settings.
4. Create a Short free text question where you want these pictures to be displayed.
5. Figure out the SGQA Identifier of this question (e.g., 12345X67X89).
6. Paste the following code as question text after activating the editor's Source view and replace both SSSSSXGGXQQ
strings with your SGQA identifier:

Please have a look at this picture:

<br />

<script>

$(document).ready(

function(){

// Find a random number (here between 1 and 4)

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

// Save the number as answer of this question

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

// Hide this answer field

document.getElementById("answerSSSSSXGGXQQ").style.display='none';
// Show the picture

picString = '<img src="pic/' + String(randNumber) + '.jpg">';

document.getElementById('pic').innerHTML = picString;

);</script>

<p>

</p>

<div id="pic">

</div>
If you want to display the number of this picture in a later question, use the following code as question text (don't forget
to replace the SSSSSXGGXQQ string acordingly):

You saw picture number {INSERTANS:SSSSSXGGXQQ}.


Perhaps you want to display the same picture again in a later question, but a little bit smaller (100px width, 100px
height)? Just paste the following code as question text after activating the editor's Source view, and replace the
SSSSSXGGXQQ string acordingly:

Here you see the picture again: <br>


<script>

$(document).ready(

function(){

// Get the picture number from previous question

var randNumber = "{INSERTANS:SSSSSXGGXQQ}";

// Show picture

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

document.getElementById('pic').innerHTML = picString;

);</script>

<div id="pic">

</div>
Multiple numerical input with max_num_value defined in
token (personalized limit)
Tested in 1.85+

As of version 1.92, max_num_value can be an experession, so you can simply enter the {TOKEN} or other variable in that
question attribute.
To make personalized survey with multiple numerical input when max_num_value (Maximum sum value) is different for
each user and is loaded from the token use following tricks:
Create 1st group with numerical input field (1000X10X11). "Limit"
Add java script into this question:
<script type="text/javascript" charset="utf-8">

function Custom_On_Load(){

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

document.getElementById('answer1000X10X11').readOnly=1;

</script>
where:
TOKEN:ATTRIBUTE_1 is individual limit defined in token.
readOnly=1 // user shouldn't change it.
Create 2nd group with multiple numerical input and add "Max value from SGQA" with 1000X10X11 as argument.
Note: If you don't want to show first field with numerical input (and show definied limit in different way) you can add 2
lines to above script to hide whole question:

document.getElementById('question85').style.display='none';

document.getElementById('display85').value='';
where:
question85 and display85 are field names.
Note: It's no possible to create it into one group, because all SGQA references can only refer to data on a previous page
because when clicking next the answer is stored at the DB and the SGQA is replaced by querying the database.

Default values in array questions


Tested with: 2.05+
This workaround uses JavaScript to allow you to pre-check default answers in all 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 script.
2. Create the array question.
3. In the source of the array question add the following script.
4. Replace"QQ" with the question ID and "CC" with the column number to be checked.
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
// A function to pre-check a column of an array
function checkedDefault(qID, column) {
var checkedCol = column - 1;
$('#question'+qID+' tr.subquestions-list').each(function(i) {
if ($('input.checkbox:checked', this).length == 0) {
$('input.checkbox:eq('+checkedCol+')', this).prop('checked',
true);
$('input.checkbox:eq('+checkedCol+')',
this).parent().find('input[type="hidden"]').val(1);
}
});
}
// Call the function with a question ID and column number
checkedDefault(QQ, CC);
});

</script>

Default Values In One Scale Of A Dual-scale Array


Question
Tested with: 2.0+, IE 9/10, Firefox 24
Overview
This workaround uses JavaScript to allow you to pre-check default answers in all sub-questions of one scale of a dual-
scale-array type question. You can select a scale and a column of that scale to be checked. The script first checks to see if
a sub-question has already been answered and, if not, checks the default answer.
Implementation:
1. Set up your survey to use script.
2. Create the array question.
3. In the source of the array question add the following script.
4. Adjust "defaultAnsweredScale" and "defaultAnsweredColumn" as necessary.
<script type="text/javascript" charset="utf-8">

$(document).ready(function() {

// Scale to set the default answer on


var defaultAnsweredScale = 2;
// Column of that scale to set as default answer
var defaultAnsweredColumn = 3;

// Identify this question


var thisQuestion = $('#question{self.qid}');

// Identify the scales and columns


$('.answer-item[class^="answer_cell_1"]', thisQuestion).addClass('scale-1-item');
$('.answer-item[class^="answer_cell_2"]', thisQuestion).addClass('scale-2-item');
$('tr.answers-list', thisQuestion).each(function(i) {
$('.scale-1-item', this).each(function(i) {
$(this).addClass('scale-1-column-'+(i+1)+'-item');
});
$('.scale-2-item', this).each(function(i) {
$(this).addClass('scale-2-column-'+(i+1)+'-item');
});
});

// Click the default answers


$('tr.answers-list', thisQuestion).each(function(i) {
if($('.scale-'+defaultAnsweredScale+'-item input[type="radio"]:checked',
this).length == 0) {
$('.scale-'+defaultAnsweredScale+'-column-'+defaultAnsweredColumn+'-item
input[type="radio"]:last', this).click();
}
});
});
</script>

Download sample survey:


Dual_scale_with_default_answers.lss

Record group view time


Tested with: 1.87+ (8518), IE 6/7, Firefox 3.5, Safari 3.2

As of version 1.92, group view time is accurately recorded in the timings table if you choose the Record Timings survey
option.
This workaround uses JavaScript to populate a hidden question with the total time that a respondent views a group. If
used in a group with only one question, it could be used to track the amount of time required to answer the question.
Implementation is as follows:
1. Set up your survey to use JavaScript.
2. In the group, create a short-text question. We will hide this later with JavaScript and populate it with the elapsed
time.
3. Place the following script in the source of the short-text question.
4. Replace "QQ" in line 6 with the ID of the short-text question.
The script populates the hidden question with the elapsed time since the group was opened. If a respondent leaves the
group and then returns, the timer continues up from the elapsed time of the last visit.
<script type="text/javascript" charset="utf-8">

$(document).ready(function(){

// call the functions with the hidden timer question ID

runTimer(QQ);

function runTimer(timeQID) {

$('#question'+timeQID).hide();

var initSec = '0';

// Check for elapsed time from previous visits

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

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

initTimeArr = initTime.split(':');

initSec = Number[[initTimeArr[0]*3600]]+Number[[initTimeArr[1]*60]]
+Number(initTimeArr[2]);
}

// Run the timer update function

recordTime(initSec, timeQID);

});

// Timer update function

function recordTime(elapsedSec, timeQID){

elapsedSec++;

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

var m = Math.floor(elapsedSec % 3600 / 60);

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


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

$('#question'+timeQID+' input.text').val(elapsedTime);

// Run the timer update function every second

setTimeout('recordTime('+elapsedSec+', '+timeQID+')',1000);

</script>

Toggle visibility of groups


Tested with: 1.90+, IE 6/7/8, Firefox 3.6, Safari 3.2
This workaround uses JavaScript to toggle the visibility of groups in an "all in one" survey. Initially the groups are hidden
and there are links inserted to display the groups. It's useful to avoid the page being cluttered with many groups and lets
the respondent focus on one at a time.
It's demonstrated here.
Implementation is as follows:
1. Set up your survey to use JavaScript.
2. Set the survey to run in "all in one" mode.
3. Add your groups and questions.
4. Place the following script in the source of one of the questions in the first group.

The comments in the script are pretty self-explanatory but it does the following:
1. initially hides all of the groups
2. inserts a link above each one to toggle its display
3. some styles are added to the clickable links but these could be done in template.css instead
4. shows any groups that have an unanswered mandatory after a submit attempt

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

$(document).ready(function(){

// Insert show/hide divs before all group wrapper divs


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

// Add some text to the show/hide divs


$('.groupToggler span').each(function(i) {
($( this ).text('Show/Hide Group '+(i+1)+''));
});

// Add some styles to the show/hide divs


$('.groupToggler').css({
'padding':'5px 0 10px 0',
'text-align':'center'
});
$('.groupToggler span').css({
'text-decoration':'underline',
'cursor':'pointer'
});
// Add some hover effects to the show/hide divs
$(".groupToggler span").hover(
function () {
$(this).css({
'text-decoration':'none',
'font-weight':'bold'
});
},
function () {
$(this).css({
'text-decoration':'underline',
'font-weight':'normal'
});
}
);

// Initially hide all of the groups


// Toggle their visibility when the show/hide is clicked
$('.groupToggler span').click(function() {
$(this).parent().next().toggle();
return false;
}).parent().next().hide();

// Display any groups that have unanswered mandatories


$('.errormandatory').parents('div[id^="group-"]').show();

// Some tidying up
$('h1').next().next().hide();
$('.surveywelcome').css({
'margin-bottom':'10px'
});
$('.surveywelcome').next().hide();
$('.surveywelcome').next().next().hide();

});

</script>

Use jQuery Autocomplete plugin to suggest answers for


text inputs

1.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.
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
Place a PHP file containing the code below in your template directory
Replace "countries2.csv" with your CSV filename (you could make this more universal by passing the filename in
the URL)
This file will grab the contents of the CSV and encode them in JSON format that the JavaScript can use
Place the JavaScript code below in ths source of the text question
Replace "QQ" with the ID of the text input question
Replace "templates/yourTemplate/countries.php" with the path to the PHP file (from LimeSurvey root)
This script will grab the jsonized data from the PHP file and load it into an array that the plugin can use
PHP code:

<?php

$countriesArr = array();

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

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

fclose($file_handle);

echo json_encode($countriesArr);

?>
JavaScript code:

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


$(document).ready(function() {

var qID = QQ;


var url = "templates/yourTemplate/countries.php";
var dataArr = [];

$.getJSON(url,function(data){

$.each(data,function(i, item){
dataArr.push(item);
});

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

});

});

</script>
Update for LimeSurvey 2.05+
Tested with: 2.05+, Chrome 40, FireFox 36, Opera 27, IE 11
With LimeSurvey 2.05+ it is NOT necessary to:
1. Download the jQuery Autocomplete widget
2. Add special autocomplete styling to the survey template (tested with the Skeletonquest template by Denis Chenu)
This workaround builds on the previous examples, but is updated for use with newer versions of LimeSurvey. Note that
theAutocomplete widget is already included in LimeSurvey, as part of jQuery UI.

Autocomplete may be configured to use different types of data input, three of which will be demonstrated in the following:
Plain text, CSV-files, and MySQL databases.

1. Plain text input (the easiest, suitable for small lists or arrays).
To enable autocomplete of a list in plain text, simply add the following code to your question source. You will have to
change qID to the question ID of the question you want the autocomplete to run in (this does not have to be the one you
add the script to). In this case, the list has been added to the source directly, but you may also call the list as a predefined
variable (see jQuery website for example):

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

$(document).ready(function() {

var qID = 1;

$('#question'+qID+' input[type="text"]').autocomplete({
minLength: 2, // This line is optional, signifies number of keystrokes before
autocomplete is initiated
source: ["Test1","Test2","Test3"]
});
});

</script>

2. Input from CSV file (a little trickier, requires several files. Suitable for larger lists and arrays).
Please note that, with this method, all processing happens browser-side. Thus, the entire contents of your CSV file will be
exposed, no matter where you "hide" it (it is not possible to request files from outside the webroot on your server using
javascript). If any of this concerns you, please proceed to the next paragraph, which deals with autocomplete based on
database queries. To enable autocomplete based on output from a CSV file, you will need the following:
1. A comma-separated data file (CSV)
2. The jquery.csv.js plugin (v0.71)
First, download the jquery.csv.js script, and place it in the template folder (may require manual FTP, rather than upload
through the template manager). You will need to call this script in the head section of the survey page, which you find in
startpage.pstpl. Make sure you call it before {TEMPLATEJS}.

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

To make the CSV file, you may use Microsoft Excel and "Save as CSV". Note that Excel produces CSV files where the
columns are separated by a semicolon, not a comma. To make this work with the jquery.csv.js plugin, you will have to
edit the jquery.csv.js file on line 43:

41 $.csv = {
42 defaults: {
43 separator:',', // Change this to: separator:';',
44 delimiter:'"',
45 headers:true
46 },

Once the CSV file is in place, add the following javascript to your question source, using the correct question ID (qID) and
url/filename for your CSV file:
<script type="text/javascript" charset="utf-8">

$(document).ready(function() {

var qID = 1;

var surveyRoot = location.pathname.split('index.php')[0];


var url = surveyRoot+'datafile.csv';

// Create an array to hold the data


var testArr = new Array();

// Grab the CSV contents


$.get(url,function(data){

// Convert CSV contents to an array of arrays


fullArray = $.csv.toArrays(data);

// Load the data array


$(fullArray).each(function(i, item){
testArr.push(item[0]);
});
// Initialise the autocomplete plugin
$('#question'+qID+' input.text').autocomplete({
source: testArr
});

// console.log(data); This would be a gould place to put your debugging script.


Remember to comment out or remove before going into production, to economize on server workload
});
});

</script>

Now, the autocomplete should work. If not, see if your CSV file shows up under "Network" or "Resources" in your browser's
inspector, and if it provides any output. Adding console.log(data); to your javascript (as indicated) should output the
data to the browser console (you'll see the contents of your CSV file), and may be useful for debugging. Note that only the
data from the current function (the function, within which the text was placed) will be presented.

3. Input from a MySQL database (trickiest, but also the most versatile and secure).
This method requires the following:
1. Access to a MySQL database (tested with MySQL 5.5)
2. A PHP script to connect to and query the database
First, paste the following script in your question source code, replacing for the correct question ID (qID) and the
url/filename of your master PHP file (which in this case is autocomplete.php, see below):

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

$(document).ready(function() {

var qID = 1;
$('#question'+qID+' input[type="text"]').autocomplete({
minLength: 2, // This line is optional, signifies number of keystrokes before
autocomplete is initiated
source: 'autocomplete.php'
});

});

</script>

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

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

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

// Connect and query MySQL database, using PDO


try {
$db = new PDO(DB_DRIVER . ":dbname=" . DB_DATABASE . ";host=" . DB_SERVER . ";charset=utf8",
DB_USER, DB_PASSWORD);
}

catch(PDOException $e) {
echo $e->getMessage();
}

$return_arr = array();

if ($db)
{
$ac_term = "%".$_GET['term']."%";
$query = "SELECT * FROM table_name WHERE column_name LIKE :term";
$result = $db->prepare($query);
$result->bindValue(":term",$ac_term);
$result->execute();

// Fetch data and store in array


while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
$row_array['label'] = $row['column_name'];
$row_array['value'] = $row['column_name'];

array_push($return_arr,$row_array);
}
}

Finally, a PHP file (autocomplete.php) to include the previous two files and complete the query. This is the file that is called
by the javascript, and the only one placed in the webroot:

<?php

// Include files outside of webroot


set_include_path('/var/www/surveys/test-survey');

// Fetch database credentials


require_once ('constant.php');

// Connect to database and perform query


require_once ('database.php');

// Clear query
$db = null;

// Encode results in JSON. This is required for jquery autocomplete


echo json_encode($return_arr);

?>

This should complete your setup, but there are obviously a variety of pitfalls, at which point the previously outlined
debugging techniques may be of help.

Use autocomplete to populate several fields


Tested with: 1.91+, IE 7/8/9, Firefox 18
This workaround uses the Autocomplete widget included with jQuery UI and the jquery.csv.js plugin to provide a
respondent with a list of suggested answers as they type in a text input and then to automatically populate other fields
depending on the selection.
For example, if you have 3 questions - "Name", "ID" and "Email" - you can use this workaround to autocomplete the
"Name" question and then the "ID" and "Email" questions can be automatically populated with values associated with the
selected name.
Download the jquery.csv.js plugin and place it in your template folder
If using LimeSurvey 1.9x, add the following line to your startpage.pstpl BEFORE the tag for template.js, if using
2.x, add it AFTER the {TEMPLATEJS} placeholder
<script type="text/javascript" src="{TEMPLATEURL}jquery.csv.js"></script>

Create your CSV file with 3 columns (name, ID, email) as in this example and place it in your template folder
Bob, Bob's ID, Bob's email

Andy, Andy's ID, Andy's email

Sam, Sam's ID, Sam's email

Tony, Tony's ID, Tony's email


Fred, Fred's ID, Fred's email

Create your 3 questions - "Name", "ID" and "Email". They must be in the same group/page.
Add the following styles to the end of your 'template.css' file
/* Autocomplete styles */

ul.ui-autocomplete {

width: 250px !important;

padding: 5px 10px;

list-style: none;

Add the following script to the source of one of the questions


Replace:
o "NN" with the "Name" question ID
o "II" with the "ID" question ID
o "EE" with the "Email" question ID
Modify the path to your CSV file
<script type="text/javascript" charset="utf-8">

$(document).ready(function() {

var qNameID = NN;


var qIDID = II;

var qEmailID = EE;

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

// Create an array to hold the names

var namesArr = new Array();

// Grab the CSV contents

$.get(url,function(data){

// Convert CSV contents to an array of arrays

fullArray = $.csv.toArrays(data);

// Load the names array

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

namesArr.push(item[0]);

});

// Initialise the autocomplete plugin

$('#question'+qNameID+' input.text').autocomplete({

source: namesArr,
// Event fired when a selection is made (ui.item.value refers to the selected item)

select: function(event, ui) {

// Find the "ID" and "Email" values associated with the selected name

value and load those questions

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

if(item[0] == ui.item.value) {

// The value from column 2 of the CSV

$('#question'+qIDID+' input.text').val(item[1]);

// The value from column 3 of the CSV

$('#question'+qEmailID+' input.text').val(item[2]);

});

});

});

});

</script>

Drag and Drop Rankings

Deprecated : This features is deprecated in version 2.0.


Hint: In 2.0 and up, this system Ranking question use jqueryui-sortable. This workaround is deprecated.

Drag and Drop Ranking - Version 1.90 to 1.92


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

Hint: In 2.0 and up, this system Ranking question use jqueryui-sortable. This workaround is deprecated.

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.

o 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);
$(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>
Fix for later versions of 1.91+
In my version of LimeSurvey 1.91+, build 11026, this drag-n-drop workaround was not saving the selections properly. I'm
not sure if that's because of my template, or other jquery uses, but I fixed it by making the following modifications to the
above code placed into template.js.
If you're having problems (and it's definitely worth testing that the rankings are actually saving into your response table
before proceeding with the survey) give this a try.
In the 'updateDragDropRank(qID)' function, replace the line:

$('#question'+qID+' input[name<div class="simplebox">="RANK_"]').each(function(index) {


with:

$('#question'+qID+' input[name^="RANK_"]').each(function(index) {
In the 'loadDragDropRank(qID) function, replace the line:

$('#question'+qID+' input[name</div>="RANK_"]').each(function(index) {
with:

$('#question'+qID+' input[name^="RANK_"]').each(function(index) {

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 to 1.92


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

Hint: In 2.0 and up, this system Ranking question use jqueryui-sortable. This workaround is deprecated.

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

Hint: In 2.0 and up, this system Ranking question use jqueryui-sortable. This workaround is deprecated.
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^='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 LimeSurvey Versions 2.06 and 2.54
This workaround uses JavaScript/jQuery to make the last option of each Array (Numbers) (Checkboxes) row exclude all
other options in the same row.
The script will uncheck all options in a row if the last option is checked and uncheck the last option in a row if any other
options are checked.

Implementation LS Version 2.06:


1. Set up your survey to use JavaScript.
2. Place the following script in the source of the array question.
3. <script type="text/javascript" charset="utf-8">

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

5. // Call the exclude function using question ID

6. excludeOpt({QID});

7. });

8.

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

10. function excludeOpt (qID) {

11.

12. var thisQuestion = $('#question'+qID)

13.
14. // Add some classes to the checkbox cells

15. $('td.checkbox-item', thisQuestion).addClass('normalOpt');

16. $('tr.subquestions-list', thisQuestion).each(function(i) {

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

18. });

19.

20. // A listener on the checkbox cells

21. $('td.checkbox-item', thisQuestion).click(function (event) {

22. handleExclusive($(this));

23. });

24.

25. // A listener on the checkboxes

26. $('td.checkbox-item input[type="checkbox"]', thisQuestion).click(function (event) {

27. handleExclusive($(this).closest('td'));

28. });

29.

30. function handleExclusive(thisCell) {

31.

32. var thisRow = $(thisCell).closest('tr');

33.

34. // Uncheck the appropriate boxes in a row

35. if ($(thisCell).hasClass('normalOpt')) {

36. $('.exlusiveOpt input[type="checkbox"]', thisRow).attr('checked', false);

37. }
38. else {

39. $('.normalOpt input[type="checkbox"]', thisRow).attr('checked', false);

40. }

41.

42. // Check conditions (relevance)

43. $('td.checkbox-item', thisRow).each(function(i) {

44. var thisValue = '';

45. if($('input[type="checkbox"]', this).is(':checked')) {

46. thisValue = 1;

47. }

48. var thisSGQA = $('input[type="checkbox"]', this).attr('id').replace(/cbox_/, '');

49.

50. $('input[type="hidden"]', this).attr('value', thisValue);

51. fixnum_checkconditions(thisValue, thisSGQA, 'hidden');

52. });

53. }

54. }

55. </script>

Implementation LS Version 2.54:


1. Set up your survey to use JavaScript.
2. Place the following script in the source of the array question.
3. <script type="text/javascript" charset="utf-8">

4. $(document).ready(function() {
5. // Call the exclude function using question ID

6. excludeOpt({QID});

7. });

8.

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

10. function excludeOpt (qID) {

11.

12. var thisQuestion = $('#question'+qID)

13.

14. // Add some classes to the checkbox cells

15. $('td.checkbox-item', thisQuestion).addClass('normal-item');

16. $('tr.subquestion-list', thisQuestion).each(function(i) {

17. $('.normal-item:last', this).removeClass('normal-item').addClass('exlusive-item')

18. });

19.

20. // A listener on the checkboxes

21. $('input[type="checkbox"]', thisQuestion).on('change', function (event) {

22. handleExclusive($(this).closest('td'));

23. });

24.

25. function handleExclusive(thisCell) {

26.

27. var thisRow = $(thisCell).closest('tr');

28.
29. // Uncheck the appropriate boxes in a row

30. if ($(thisCell).hasClass('normal-item')) {

31. $('.exlusive-item input[type="checkbox"]', thisRow).attr('checked', false);

32. }

33. else {

34. $('.normal-item input[type="checkbox"]', thisRow).attr('checked', false);

35. }

36.

37. // Check conditions (relevance)

38. $('td.checkbox-item', thisRow).each(function(i) {

39. var thisValue = '';

40. if($('input[type="checkbox"]', this).is(':checked')) {

41. thisValue = 1;

42. }

43. var thisSGQA = $('input[type="checkbox"]', this).attr('id').replace(/cbox_/, '');

44.

45. $('input[type="hidden"]', this).attr('value', thisValue);

46. fixnum_checkconditions(thisValue, thisSGQA, 'hidden');

47. });

48. }

49. }

50. </script>
Add Navigation Buttons (Next and Previous) To Top Of
Page
Tested with: 1.90+, IE 7/8, Firefox 3.6, Safari 3.2
Add the following to the end of template.js. It will insert a new div after the progress bar and then insert clones of the nav
buttons in the new div.
This example is for the default template in 1.90 or 1.91. The placement of the new div may need to be modified for other
templates.

$(document).ready(function() {

// Insert a new div after the progress bar


$('<div id="navigator2" />').insertAfter('#progress-wrapper');

// Style the new div


$('#navigator2').css({
'text-align':'center'
});

// Insert clones of the nav buttons in the new div


$('input.submit:eq(0)').clone().appendTo('#navigator2');
$('input.submit:eq(2)').clone().appendTo('#navigator2');

// Give the new buttons some new IDs


$('#navigator2 input.submit').each(function(i) {
var oldID = $(this).attr('id');
var newID = oldID + '2';
$(this).attr('id', newID)
});
});

How to add an opt out link to your survey


Tested with: 1.91+, Firefox 5.0
This javascript inserts a link to opt out of the survey above the progress bar.
How to use script here
Then insert the following code into to the source of a group description or a question. I used the group description.

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


$(document).ready(function() {

// Find the survey ID


if($('input#fieldnames').length != 0) {
var fieldNames = $('input#fieldnames').attr('value');
var tmp = fieldNames.split('X');
var sID = tmp[0];
}

// Insert a new div after the progress bar


$('<div id="optOutLink" />').insertBefore('#progress-wrapper');

// Style the new div


$('#optOutLink').css({
'text-align':'center'
});

// Insert opt out url in the new div


$('#optOutLink').html('<p><a href="optout.php?lang=en&sid;='+sID+'&token;={TOKEN}">Click
here to opt out of this survey.</a></p><br>');

});

</script>
Anonymously track respondents answers across
multiple surveys
Tested with: 1.91+ and 1.92+
This workaround uses JavaScript/jQuery to enable tracking of respondents across multiple surveys ensuring anonymity (it
does not use tokens).
To do this you have to use this question in all the surveys you want to link respondents answers.
This workaround create an ID by asking some partial personal information (like initials, birth month ...). The ID will be
encrypted using a one-way function (in this case SHA-1) and stored in a short text answer.
Implementation is as follows:
1. Set up your survey to use JavaScript.
2. Place the following code in the source of a short text question.
3. Optional: you can also add other select field to avoid collisions (e.g. for large population sample)
<p>This answers are used to generate your ID. These information will be encrypted, it will not be
possible in any case to identify you neither from your answers nor from your ID.</p>

<table>

<tr>

<td>Initial of your name</td>

<td>

<select id="field1">
<option value=""></option>

<option value="A">A</option>

<option value="B">B</option>

<option value="C">C</option>

<option value="D">D</option>

<option value="E">E</option>

<option value="F">F</option>

<option value="G">G</option>

<option value="H">H</option>

<option value="I">I</option>

<option value="J">J</option>

<option value="K">K</option>
<option value="L">L</option>

<option value="M">M</option>

<option value="N">N</option>

<option value="O">O</option>

<option value="P">P</option>

<option value="Q">Q</option>

<option value="R">R</option>

<option value="S">S</option>

<option value="T">T</option>

<option value="U">U</option>

<option value="V">V</option>

<option value="W">W</option>
<option value="X">X</option>

<option value="Y">Y</option>

<option value="Z">Z</option>

</select>

</td>

</tr>

<tr>

<td>Your birth month</td>

<td>

<select id="field2">

<option value=""></option>

<option value="01">January</option>
<option value="02">February</option>

<option value="03">March</option>

<option value="04">April</option>

<option value="05">May</option>

<option value="06">June</option>

<option value="07">July</option>

<option value="08">August</option>

<option value="09">September</option>

<option value="10">October</option>

<option value="11">November</option>

<option value="12">December</option>

</select>
<td>

</tr>

</table>

<script type="text/javascript" src="http://crypto-


js.googlecode.com/svn/tags/3.0.2/build/rollups/sha1.js"></script>

<script type="text/javascript">

$(document).ready(function() {

$("#answer{SGQ}").attr('readonly', 'readonly');

//$("#answer{SGQ}").css('display', 'none'); //uncomment this line for not displaying the code

});

$('#field1, #field2').bind('change keyup', function() {

if ($("#field1").val()=='' || $("#field2").val()=='')

$("#answer{SGQ}").val('');
else {

var sha1 = CryptoJS.algo.SHA1.create();

sha1.update($("#field1").val());

sha1.update($("#field2").val());

// sha1.update($("#otherfield").val());

var hash = sha1.finalize();

$("#answer{SGQ}").val(hash);

});

</script>

Check validity of international phone number in


subquestion
Tested with: 1.91+, Firefox 8, Chrome 15
As of version 1.92, you can use the core Validation querstion option to validate multiple short text and array text
questions. Any question that fails that validation check will have its background color changed to red.
Note, the below workaround will fail in version 1.92 because it contains a regular expression with curly braces. However,
as described above, support for this feature is now built-in to version 1.92
This workaround uses javascript to check the validity of an international phone number in a subquestion of multiple short
text.
Add the following code to the source of your question.
Replace "QQ" with the question ID and "NN" with the row number that you want to validate.

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

$(document).ready(function() {

// Call the phone function with the question ID and the row number

phoneTest(QQ, NN);

function phoneTest(qID, inputNum) {

// Interrupt next/submit function

$('#movenextbtn, #movesubmitbtn').click(function(){

// Some vars - modify as required

var phoneMatch = /^[0-9,+,(), ,]{1,}(,[0-9]+){0,}$/;


var msg1 = 'Please enter a valid phone number.';

// Test the input

var phoneInput = $('#question'+qID+' li:eq('+(inputNum-1)+') input.text').val();

if(phoneInput != '' && !phoneMatch.test(phoneInput)) {

alert(msg1);

$('#question'+qID+' li:eq('+(inputNum-1)+') input.text').css({


'background':'pink' });

return false;

else {

$('#question'+qID+' li:eq('+(inputNum-1)+') input.text').css({ 'background':'' });

return true;
}

});

});

</script>
The regex is from: regexlib.com blank

Check validity of email address in subquestion


'Tested with: 1.91+, Firefox 8, Chrome 15

As of version 1.92, you can use the core Validation querstion option to validate multiple short text and array text
questions. Any question that fails that validation check will have its background color changed to red.
Note, the below workaround will fail in version 1.92 because it contains a regular expression with curly braces. However,
as described above, support for this feature is now built-in to version 1.92
This workaround uses javascript to check the validity of an email address in a subquestion of multiple short text.
Add the following code to the source of your question.
Replace "QQ" with the question ID and "NN" with the row number that you want to validate.

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

$(document).ready(function() {
// Call the email function with the question ID and the row number

emailTest(QQ, NN);

function emailTest(qID, inputNum) {

// Interrupt next/submit function

$('#movenextbtn, #movesubmitbtn').click(function(){

// Some vars - modify as required

var emailMatch = /^[a-zA-Z0-9\_\-]+[a-zA-Z0-9\.\_\-]*@([a-zA-Z0-9\_\-]+\.)+([a-zA-Z]


{2,4}|travel|museum)$/;

var msg1 = 'Please enter a valid email address.';

// Test the input

var emailInput = $('#question'+qID+' li:eq('+(inputNum-1)+') input.text').val();

if(emailInput != '' && !emailMatch.test(emailInput)) {


alert(msg1);

$('#question'+qID+' li:eq('+(inputNum-1)+') input.text').css({


'background':'pink' });

return false;

else {

$('#question'+qID+' li:eq('+(inputNum-1)+') input.text').css({ 'background':'' });

return true;

});

});

</script>
The regex is from: regexlib.com blank

Hiding the help-text when it's empty


Tested with: 1.91+
I had made a style in my template putting a border around my help-text. To avoid empty help-text to show up (and have
lines instead of just 'nothing' when there was no help text, I used this piece of code in question.pstpl:

<script type="text/javascript">

var questionhelp="{QUESTIONHELP}";

if (questionhelp=="") {

document.write("");}

else {

document.write('<tr><td class="survey-question-help">'+ questionhelp


+'</td></tr>');}

</script>
it can probably be optimized but it worked for me

Disable or hide selected fields in a matrix question


Tested with: 1.92+, IE 7/8/9, FireFox 12/13
TASK
Hide or disable selected fields in a Matrix, so that users cannot make any input.
(See example, where users should not make input into red framed fields)

Fig. 1: Prevent users from input into first two fields (as an example)
SOLUTION
First find the name of the fields you want to hide / disable. In my case I used Firebug with firefox to figure that out.
Fig. 2: Figure out the name(s) of the field(s)
Then put the following script-code into the HTML-source code as described above:
Note: you have to change the code to match the names of your fields!

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

$(document).ready(function() {

$('input[name="69965X2X10SQ001_SQ001"]').attr('disabled', 'disabled');

$('input[name="69965X2X10SQ001_SQ002"]').attr('hidden', 'hidden');

});

</script>
Fig. 3: Result: first field is diabled ("greyed out"), second one is completely hidden
I think this way one can apply many other attributes to a field like color etc. But did not try that myself.
Thanks to roB2009 for the input in the forum!

Add prefix or suffix to question type "Array (Texts)"


TASK
Add a prefix or suffix to the text fields in questions of the "Array (Texts)" type.
Fig. 1: Suffix

Fig. 1: Prefix
SOLUTION
Set up your survey to use JavaScript.
Add the following script to the source of the array. Replace "12345" with the array question ID and modify the
suffixes/prefixes as required.
The function call must consist of a question ID followed by the suffixes for each column. Suffixes/Prefixes must
be enclosed in quotes and comma separated.
In this example question ID 12345 will get an "in " suffix in the first column and an "in %" in the next 3
columns.
<script type="text/javascript" charset="utf-8">

$(document).ready(function() {

// Call the function with the question ID followed by a comma-separated list of suffixes

addSuffix(12345, 'in &euro;', 'in %', 'in %', 'in %');


function addSuffix() {

var qID = $(arguments)[0];

// Assign some column-specific classes

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

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

$(this).addClass('answerCol-'+(i+1)+'');

});

});

// Some styling

$('#question'+qID+' table.question tbody input[type="text"]').css({

'width': '50%'

});
// Insert the suffixes

$(arguments).each(function(i, val){

if(i > 0) {

$('#question'+qID+' td.answerCol-'+i+' label').append(val);

});

});

</script>
For adding a prefix you need to substitute the following line in the code

$('#question'+qID+' td.answerCol-'+i+' label').append(val);


with this

$('#question'+qID+' td.answerCol-'+i+' label').prepend(val);

Time question (Date question-like)


Tested with: 1.92+
This workaround uses JavaScript/jQuery to build a Time question just like to the Date one.
Implementation is as follows:
1. Download the file at trentrichardson.com/examples/timepicker/jquery-ui-timepicker-addon.js and upload it to your
survey.
2. Set up your survey to use JavaScript.
3. Place the following code in the source of a short text question.
<script src="/upload/surveys/{SID}/jquery-ui-timepicker-addon.js"></script>

<script>

$(document).ready(function(){

$('#answer{SGQ}').timepicker({
timeText: 'Time', //here translation
hourText: 'hour', //here translation
minuteText: 'minute', //here translation
closeText: 'Done' //here translation
});

$('#answer{SGQ}').attr('readonly', 'readonly');

$( "<style>"
+".ui-widget-header, .ui-datepicker-current { display:none; }"
+".ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; }"
+".ui-timepicker-div dl dd { margin: 0 10px 10px 65px; }"
+" }"
+"</style>").appendTo( "head" );

});

</script>

Calculating date difference


Tested with: 1.92+ Build 120909
This workaround explains how calculate difference between two dates (in: days, weeks, months and years) and is based
on thisresource external.
Requieres 2 groups and 'group by group' or 'question by question' presentation.
Group 1 (2 date questions)
1st question - Question code: "DD"
2nd question - Question code: "D2"
Group 2
1 numerical input question.
This question retrieves the number of days between the 2 dates, and check if D2 is posterior than DD.
All the requiered code have to be added in the source of this numerical question.

<script type="text/javascript">

/*

Adapted from http://ditio.net/2010/05/02/javascript-date-difference-calculation/


*/
var DateDiff = {

inDays: function(d1, d2) {

var t2 = d2.getTime();

var t1 = d1.getTime();

return parseInt[[t2-t1)/(24*3600*1000]];

},

inWeeks: function(d1, d2) {

var t2 = d2.getTime();

var t1 = d1.getTime();

return parseInt[[t2-t1)/(24*3600*1000*7]];

},

inMonths: function(d1, d2) {


var d1Y = d1.getFullYear();

var d2Y = d2.getFullYear();

var d1M = d1.getMonth();

var d2M = d2.getMonth();

return (d2M+12*d2Y)-(d1M+12*d1Y);

},

inYears: function(d1, d2) {

return d2.getFullYear()-d1.getFullYear();

d1 = new Date({substr(DD,0,4)}, {substr(DD,5,2)}, {substr(DD,8,2)});

d2 = new Date({substr(D2,0,4)}, {substr(D2,5,2)}, {substr(D2,8,2)});


// Displays result

document.write("<br />Number of <b>days</b>: "+DateDiff.inDays(d1, d2));

document.write("<br />Number of <b>weeks</b>: "+DateDiff.inWeeks(d1, d2));

document.write("<br />Number of <b>months</b>: "+DateDiff.inMonths(d1, d2));

document.write("<br />Number of <b>years</b>: "+DateDiff.inYears(d1, d2));

// filled the numerical input with number of days (or nights)

var nDays = +DateDiff.inDays(d1, d2);

jQuery(document).ready(function() {

$(".numeric:eq(0) input.text").val(nDays);

$(".numeric:eq(0) input.text").attr('readonly','readonly');

// check if d2 > d1

$('#movenextbtn').hide();
$('#movesubmitbtn').hide();

if(nDays >= 0){

// automatic submit (uncomment if needed)

// document.limesurvey.submit();

// $('body').hide();

else alert("Check out date must be posterior than Check in\nPlease return to the
previous page and edit your answers");

});

</script>

How to set minDate/maxDate based on other


Datequestions
tested with Version 2.00+ Build 130708
QQ1 and QQ2 are the question identifiers. QQ2 gets as maxDate today and as minDate the date set in QQ1. The onClose
event is needed because on document.ready the value of QQ1 is not yet known, although the value of today is.

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


$(document).ready(function () {
'use strict';
var qID1 = QQ1,
qID2 = QQ2,
q1 = $('#question' + 'QQ1' + ' .popupdate'),
q2 = $('#question' + 'QQ2' + ' .popupdate');

//if q1 loses focus, its value is taken and used as minDate in q2


q1.datepicker("option", "onClose", function (datum){
q2.datepicker( "option", "minDate", datum );
});
//q2 gets today as its maxDate
q2.datepicker( 'option', 'maxDate', new Date() );
});
</script>

Remove Question Help if empty


Version 2.00+ Build 130513
Change your question.pstpl in your Template Editor so that the line looks like below. I've added the ID tag
help_{question_number}.

<tr><td class="survey-question-help" id=help_{QUESTION_NUMBER}>{QUESTIONHELP}</td></tr>


You can then add the following javascript at the end of question.pstpl. If the contents of the help tag is empty then hide
the element.

<script>
$(document).ready(function(){
if($('#help_{QUESTION_NUMBER}').is(':empty')){
$('#help_{QUESTION_NUMBER}').hide();
}
});
</script>

Implicit Association Test (IAT)

Version 2.06
Tested with: LimeSurvey version 2.06
This workaround uses JavaScript to insert an IAT interface into an Array (Texts) question. The workaround prompts
respondents to assign words or phrases using the "E" or "I" keys. In mobile devices, "E" and "I" buttons are provided.
Download a working template and working survey.
Implementation is as follows:
1) Set up your survey to use JavaScript.
2) Create your Array (Texts) with sub-questions as follows:
- Y scale - all of your "attribut" words or phrases
- X scale - "Key Pressed" and "Elapsed Time (ms)"

3) Insert your association terms and the other text elements for the interface into the Question Help section as depicted
below. The text strings must be separated with "double pipes" (||) and you will need to place them in the following order:
- Left association term
- Right association term
- Instructional text
- Finished text

4) Add the following to the end of template.js:


// A plugin to insert an IAT interface
function implicitAssociation(el, showAnswers) {

$(el).each(function() {

var thisQuestion = $(this);


var thisQuestionHelp = $('img[src$="help.gif"]', thisQuestion).parent();
var thisQuestionAnswers = $('table.question', thisQuestion).parent();
var startTime = '';
var endTime = '';
var agent = navigator.userAgent.toLowerCase();
var mobile = false;
if( /android|webos|iphone|ipad|ipod|blackberry/i.test(navigator.userAgent) ) { // Mobile
devices
mobile = true;
}

// Some classes
$(thisQuestion).addClass('iatQuestion');
$('.subquestion-list').addClass('unanswered');

// Hide the question help element


$(thisQuestionHelp).hide();

// Hide the answers


if(showAnswers != true) {
$('table.question', thisQuestion).hide();
}

// Insert IAT display


var iatTexts = $(thisQuestionHelp).text().split('||');
var iatDisplayHTML = '<div class="iatWrapper">\
<div
class="iatLeftLabel">'+iatTexts[0]+'</div>\
<div
class="iatRightLabel">'+iatTexts[1]+'</div>\
<div class="iatWord"></div>\
<div
class="iatInstructions">'+iatTexts[2]+'</div>\
</div>\
<div class="iatMobileButtonWrapper">\
<div class="iatButton
iatLeftButton">E</div>\
<div class="iatButton
iatRightButton">I</div>\
<div style="width:100%; clear:both;"></div>\
</div>';
$(thisQuestionAnswers).prepend(iatDisplayHTML);

// Show a word
function iatShowWord() {
$('div.iatWord', thisQuestion).text($('.subquestion-list.unanswered:first
.answertext', thisQuestion).text());
startTime = new Date();

$(document).bind('keypress.iatKeypress', function(e) {
if(e.which == 101 || e.which == 105) {
var thisRow = $('.subquestion-list.unanswered:eq(0)',
thisQuestion);
$(thisRow).removeClass('unanswered');
endTime = new Date();
$('input[type="text"]:eq(1)', thisRow).val(endTime.valueOf() -
startTime.valueOf());
if(e.which == 101) {
$('input[type="text"]:eq(0)', thisRow).val('E');
}
else {
$('input[type="text"]:eq(0)', thisRow).val('I');
}
$(document).unbind('keypress.iatKeypress');
if($('.subquestion-list.unanswered', thisQuestion).length > 0) {
iatShowWord();
}
else {
$('.iatLeftLabel, .iatWord, .iatRightLabel,
.iatInstructions', thisQuestion).fadeOut('slow', function() {
$('div.iatWord', thisQuestion).text(iatTexts[3]);
$('.iatWord',
thisQuestion).addClass('done').fadeIn('slow');
});
}
}
});
}
function iatShowWordMobile() {
$('div.iatWord', thisQuestion).text($('.subquestion-list.unanswered:first
.answertext', thisQuestion).text());
startTime = new Date();

$('.iatButton', thisQuestion).bind('click.iatButtonClick', function(e) {


var thisRow = $('.subquestion-list.unanswered:eq(0)', thisQuestion);
$(thisRow).removeClass('unanswered');
endTime = new Date();
$('input[type="text"]:eq(1)', thisRow).val(endTime.valueOf() -
startTime.valueOf());
$('input[type="text"]:eq(0)', thisRow).val($(this).text());
$('.iatButton', thisQuestion).unbind('click.iatButtonClick');
if($('.subquestion-list.unanswered', thisQuestion).length > 0) {
iatShowWordMobile();
}
else {
$('.iatLeftLabel, .iatWord, .iatRightLabel, .iatInstructions,
.iatMobileButtonWrapper', thisQuestion).fadeOut('slow', function() {
$('div.iatWord', thisQuestion).text(iatTexts[3]);
$('.iatWord',
thisQuestion).addClass('done').fadeIn('slow');
});
}
});
}

// Start the IAT display


if(mobile == true) { // Mobile devices
$('.iatMobileButtonWrapper', thisQuestion).show();
$('.iatButton', thisQuestion).bind('click.iatStartMobile', function(e) {
$('.iatButton', thisQuestion).unbind('click.iatStartMobile');
iatShowWordMobile();
});
}
else {
$(document).bind('keypress.iatStart', function(e) { // Non-mobile devices
if(e.which == 101 || e.which == 105) {
$(document).unbind('keypress.iatStart');
iatShowWord();
}
});
}
});
}

5) Add the following to the end of template.css:


/* IAT questions */

.iatWrapper {
position: relative;
width: 600px;
height: 300px;
color: #FFFFFF;
background-color: #000000;
font-size: 21px;
}

.iatLeftLabel {
float: left;
padding: 10px;
}

.iatRightLabel {
float: right;
padding: 10px;
}

.iatWord {
position: absolute;
width: 100%;
top: 35%;
text-align: center;
font-size: 36px;
color:#0F0;
}

.iatWord.done {
color:#FFFFFF;
}

.iatInstructions {
position: absolute;
width: 100%;
bottom: 0px;
padding-bottom: 10px;
text-align: center;
}

.iatMobileButtonWrapper {
display: none;
position: relative;
width: 600px;
font-size: 36px;
}

.iatButton {
float: left;
margin: 20px;
width: 100px;
padding: 2px 0 5px 0;
border: 5px solid #999;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
-khtml-border-radius: 10px;
border-radius: 10px;
text-align: center;
line-height: normal;
cursor: pointer;
}

.iatLeftButton {
float: left;
}

.iatRightButton {
float: right;
}

6) To apply the plugin, add one of the two scripts below to the question source:
- With hidden answer table (for normally activated survey)

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


$(document).ready(function() {
// Apply the IAT plugin to this question
implicitAssociation('#question{QID}');
});
</script>
- With visible answer table (for testing purposes)

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


$(document).ready(function() {
// Apply the IAT plugin to this question
implicitAssociation('#question{QID}', true);
});
</script>

Version 2.50
Tested with: LimeSurvey version 2.50
This workaround uses JavaScript to insert an IAT interface into an Array (Texts) question. The workaround prompts
respondents to assign words or phrases using the "E" or "I" keys. In mobile devices, "E" and "I" buttons are provided.
DOWNLOADS:
Template - Demo_IAT_For_25_Template.zip
Survey - Demo_IAT_For_25_Survey.zip.
IMPLEMENTATION:
1. Set up your survey to use JavaScript.
2. Create your Array (Texts) with sub-questions as follows:
o Y scale - all of your "attribute" words or phrases
o X scale - "Key Pressed" and "Elapsed Time (ms)"

3. Insert your association terms and the other text elements for the interface into the Question Help section as
depicted below. The text strings must be separated with "double pipes" (||) and you will need to place them in the
following order:
o Left association term
o Right association term
o Instructional text
o Finished text

4. Add the following to the end of template.js:


5. // A plugin to insert an IAT interface

6. function implicitAssociation(el, showAnswers) {

7.

8. $(el).each(function() {

9.

10. var thisQuestion = $(this);

11. var thisQuestionHelp = $('div.question-help', thisQuestion);

12. var thisQuestionAnswers = $('table.subquestion-list', thisQuestion).parent();

13. var startTime = '';

14. var endTime = '';

15. var agent = navigator.userAgent.toLowerCase();

16. var mobile = false;

17. if( /android|webos|iphone|ipad|ipod|blackberry/i.test(navigator.userAgent) ) { // Mobile devices

18. mobile = true;

19. }

20.
21. // Some classes

22. $(thisQuestion).addClass('iat-question');

23. $('tr.subquestion-list', thisQuestion).addClass('unanswered');

24.

25. // Hide the question help element

26. $(thisQuestionHelp).hide();

27. $(thisQuestionHelp).closest('.question-help-container').hide();

28.

29. // Hide the answers

30. if(showAnswers != true) {

31. $('table.subquestion-list', thisQuestion).hide();

32. }

33.

34. // Insert IAT display

35. var iatTexts = $(thisQuestionHelp).text().split('||');

36. var iatDisplayHTML = '<div class="iatWrapper">\

37. <div class="iatLeftLabel">'+iatTexts[0]+'</div>\

38. <div class="iatRightLabel">'+iatTexts[1]+'</div>\

39. <div class="iatWord"></div>\

40. <div class="iatInstructions">'+iatTexts[2]+'</div>\

41. </div>\

42. <div class="iatMobileButtonWrapper">\

43. <div class="iatButton iatLeftButton">E</div>\

44. <div class="iatButton iatRightButton">I</div>\


45. <div style="width:100%; clear:both;"></div>\

46. </div>';

47. $(thisQuestionAnswers).prepend(iatDisplayHTML);

48.

49. // Show a word

50. function iatShowWord() {

51. $('div.iatWord', thisQuestion).text($('tr.subquestion-list.unanswered:first .answertext',

thisQuestion).text());

52. startTime = new Date();

53.

54. $(document).bind('keypress.iatKeypress', function(e) {

55. if(e.which == 101 || e.which == 105) {

56. var thisRow = $('tr.subquestion-list.unanswered:eq(0)', thisQuestion);

57. $(thisRow).removeClass('unanswered');

58. endTime = new Date();

59. $('input[type="text"]:eq(1)', thisRow).val(endTime.valueOf() -

startTime.valueOf());

60. if(e.which == 101) {

61. $('input[type="text"]:eq(0)', thisRow).val('E');

62. }

63. else {

64. $('input[type="text"]:eq(0)', thisRow).val('I');

65. }

66. $(document).unbind('keypress.iatKeypress');
67. if($('tr.subquestion-list.unanswered', thisQuestion).length > 0) {

68. iatShowWord();

69. }

70. else {

71. $('.iatLeftLabel, .iatWord, .iatRightLabel, .iatInstructions',

thisQuestion).fadeOut('slow', function() {

72. $('div.iatWord', thisQuestion).text(iatTexts[3]);

73. $('.iatWord', thisQuestion).addClass('done').fadeIn('slow');

74. });

75. }

76. }

77. });

78. }

79. function iatShowWordMobile() {

80. $('div.iatWord', thisQuestion).text($('tr.subquestion-list.unanswered:first .answertext',

thisQuestion).text());

81. startTime = new Date();

82.

83. $('.iatButton', thisQuestion).bind('click.iatButtonClick', function(e) {

84. var thisRow = $('tr.subquestion-list.unanswered:eq(0)', thisQuestion);

85. $(thisRow).removeClass('unanswered');

86. endTime = new Date();

87. $('input[type="text"]:eq(1)', thisRow).val(endTime.valueOf() - startTime.valueOf());

88. $('input[type="text"]:eq(0)', thisRow).val($(this).text());


89. $('.iatButton', thisQuestion).unbind('click.iatButtonClick');

90. if($('tr.subquestion-list.unanswered', thisQuestion).length > 0) {

91. iatShowWordMobile();

92. }

93. else {

94. $('.iatLeftLabel, .iatWord, .iatRightLabel, .iatInstructions,

.iatMobileButtonWrapper', thisQuestion).fadeOut('slow', function() {

95. $('div.iatWord', thisQuestion).text(iatTexts[3]);

96. $('.iatWord', thisQuestion).addClass('done').fadeIn('slow');

97. });

98. }

99. });

100. }

101.

102. // Start the IAT display

103. if(mobile == true) { // Mobile devices

104. $('.iatMobileButtonWrapper', thisQuestion).show();

105. $('.iatButton', thisQuestion).bind('click.iatStartMobile', function(e) {

106. $('.iatButton', thisQuestion).unbind('click.iatStartMobile');

107. iatShowWordMobile();

108. });

109. }

110. else {

111. $(document).bind('keypress.iatStart', function(e) { // Non-mobile devices


112. if(e.which == 101 || e.which == 105) {

113. $(document).unbind('keypress.iatStart');

114. iatShowWord();

115. }

116. });

117. }

118. });

119. }

120. Add the following to the end of template.css:


121. /* IAT questions */

122.

123. .iatWrapper {

124. position: relative;

125. width: 100%;

126. max-width: 600px;

127. height: 300px;

128. margin: 0 auto;

129. color: #D0DBE5;

130. background-color: #2C3E50;

131. font-size: 21px;

132. }

133.

134. .iatLeftLabel {

135. float: left;


136. padding: 10px;

137. }

138.

139. .iatRightLabel {

140. float: right;

141. padding: 10px;

142. }

143.

144. .iatWord {

145. position: absolute;

146. width: 100%;

147. top: 35%;

148. text-align: center;

149. font-size: 36px;

150. color: #C4D1DE;

151. }

152.

153. .iatWord.done {

154. color: #C4D1DE;

155. }

156.

157. .iatInstructions {

158. position: absolute;

159. width: 100%;


160. bottom: 0px;

161. padding-bottom: 10px;

162. text-align: center;

163. }

164.

165. .iatMobileButtonWrapper {

166. display: none;

167. position: relative;

168. width: 100%;

169. max-width: 600px;

170. margin: 0 auto;

171. font-size: 36px;

172. }

173.

174. .iatButton {

175. float: left;

176. margin: 20px;

177. width: 100px;

178. padding: 2px 0 5px 0;

179. border: 5px solid #233140;

180. -moz-border-radius: 10px;

181. -webkit-border-radius: 10px;

182. -khtml-border-radius: 10px;

183. border-radius: 10px;


184. text-align: center;

185. line-height: normal;

186. cursor: pointer;

187. }

188.

189. .iatLeftButton {

190. float: left;

191. }

192.

193. .iatRightButton {

194. float: right;

195. }

196. To apply the plugin, add one of the two scripts below to the question source:
o With hidden answer table (for normally activated survey)
o <script type="text/javascript" charset="utf-8">

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

o // Apply the IAT plugin to this question

o implicitAssociation('#question{QID}');

o });

o </script>

o With visible answer table (for testing purposes)


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

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

o // Apply the IAT plugin to this question


o implicitAssociation('#question{QID}', true);

o });

o </script>

Make multiple choice easier


Tested with Version 2.00 in Chrome, FF, Safar, IE
The problem: We often have multiple choice question, where the user has to select 4 elements out of 78 possible answers
or 3 out of 25 and so on. If we use pen and paper version for this questions, we noticed that the participants go through
the whole list and if an answer doesn't suit on the first sight, they cross it out.
The solution: The script gives the participant the possibility to cross out this answers in a digital way. The script adds a
small "X" before the checkbox. When it is clicked, the label text of the checkbox turns grey. (see the attached screenshot
for an example)
Here is the script, just add it to help text

<script type="text/javascript">

/**
* Small script that adds a "X" before a checkbox.
* On click the text color of the label of the checkbox turns grey
* If the user has to choose some answers from many options, he can "delete" answers, that on first
sight looks wrong
* Add the script to the question in Limesurvey (for example within the source code of the help text)
*/

//Define the X (standard is a X in capitals)


var x = "X";
css_class = "x";

/*
* Define the style of the X
* Of course you can define the style in the stylesheet - if so, the script ignores the following
settings
* If there is a style, than set style var to 1
*/
var style = 0;
var size = "12px";
var weight = "bold";
var color = "red";
var cursor = "pointer";

//element type - standard is a span element


var type = "span";

//Define the new font color of the checkbox label


label_color = "#bfbfbf";

//End of configuration - start of the function

//Define the element that is added


if(style == 1) {
ele = "<"+type+" class='"+css_class+"'>"+x+"</"+type+">";
}
else {
ele = "<"+type+" class='"+css_class+"' style='font-size:"+size+"; font-weight:"+weight+";
color:"+color+"; cursor:"+cursor+";'>"+x+"</"+type+">";
}

//Get the standard font-color of the used style


st_color = $("label").css("color");

//insert the X before the checkboxes


$(document).ready(function() {
$("li").each(function(){
$(this).prepend(ele);
});
});

//bind an eventhandler on the X (delegate is used, because Limesurvey uses an old jQuery version)
var css_class_c = "."+css_class;
$(document).delegate(css_class_c,"click",function() {
var color_check = $(this).siblings("label").data("color");
if(color_check != "true") {
$(this).siblings("label").css("color",label_color).data("color","true");
$(this).siblings('input').attr('checked', false);

}
else {
$(this).siblings("label").css("color",st_color).data("color","false");

}
});

$(".checkbox").click(function () {
$(this).siblings("label").css("color",st_color).data("color","false");
});

</script>

Prevent the survey from being submitted when users


press enter
By default HTML forms are submitted when the user presses enter in any text field. This can be annoying with long survey
pages or if your users are inexperienced with keyboard shortcuts.
To disable this behavior, add the following code snippet to template.js:

$(document).ready(function(){
$('input').keypress(
function(event){
if (event.which == '13') {
event.preventDefault();
}
});
});

Card Sorting
Tested with: 2.05, IE 8-11, Firefox, Chrome, iPad
This workaround uses JavaScript to allow dragging items into "buckets" to "card sort" them. A multiple-short-text question
is used with sub-questions being the draggable items. The data recorded for each item is the "bucket" number that it was
dropped in. "Buckets" are pre-defined in a list in the "Help" section of the question.
DOWNLOADS:
- Demo template
- Demo survey.
IMPLEMENTATION:
1) Set up your survey to use JavaScript.
2) Create your multiple-short-text with sub-questions.
3) Insert the "bucket" labels into an unordered list in the Question Help section.

4) Add the following to the end of template.js:


function cardSort(qID) {
// Define some stuff...
var thisQuestion = $('#question'+qID);
var thisQuestionHelp = $('img[alt="Help"]', thisQuestion).parent();
thisQuestion.addClass('card-sort-question');

// Hide the "Help" section


thisQuestionHelp.hide();

//Insert the "card sort" elements


$('ul.questions-list', thisQuestion).parent().prepend('<div class="droppable items-
start"></div><div class="items-end-wrapper" />');
$('ul:eq(0) li', thisQuestionHelp).each(function(i) {
$('.items-end-wrapper', thisQuestion).append('<div class="items-end-inner">\
<div
class="items-end-text">'+$(this).html()+'</div>\
<div
class="droppable items-end items-end-'+(i+1)+'" data-items-end="'+(i+1)+'"></div>\
</div>')});

// Insert the "cards"


$('li.answer-item', thisQuestion).each(function(i) {
var thisSGQA = $(this).attr('id').replace(/javatbd/, '');
var thisCode = $(this).attr('id').split('X'+qID)[1];
var thisHTML = $('label', this).html();
$('div.items-start').append('<div class="card draggable" data-sgqa="'+thisSGQA+'" data-
code="'+thisCode+'">\
'+thisHTML+'\
</div>');
});

// Make the "cards" draggable


$('.draggable').draggable({
revert: "invalid",
zIndex: 2700,
helper: 'original',
start: function( event, ui ) {
$(ui.helper).addClass('ui-draggable-helper');
},
stop: function( event, ui ) {
}
});

// Set the targets for the draggables


$('.droppable.items-start').droppable({
hoverClass: 'target-hover',
accept: '.draggable.moved'
});
$('.droppable.items-end').droppable({
hoverClass: 'target-hover',
accept: '.draggable'
});

// After dropped
$('.droppable').bind('drop', function(event, ui) {

// Physically move the draggable to the target


// (the plugin just visually moves it)
// (need to use a clone here to fake out iPad)
var newDraggable = $(ui.draggable).clone();
$(newDraggable).appendTo(this);
$(ui.draggable).remove();
if($(this).hasClass('items-end')) {
$(newDraggable).addClass('moved');
}
else {
$(newDraggable).removeClass('moved');
}
$(newDraggable).removeClass('ui-draggable-helper ui-draggable-dragging').css({
'z-index':'',
'top':'auto',
'left':'auto'
});

// Now, make this new clone draggable


$(newDraggable).draggable({
revert: "invalid",
zIndex: 2700,
helper: 'original',
start: function( event, ui ) {
$(ui.helper).addClass('ui-draggable-helper');
},
stop: function( event, ui ) {
}
});
});

// Initial "card" positions


$('li.question-item input.text', thisQuestion).each(function(i) {
if($(this).val() > 0) {
$('.items-end[data-items-end="'+$(this).val()+'"]').append($('.card[data-sgqa="'+
$(this).attr('name')+'"]'));
$('.card[data-sgqa="'+$(this).attr('name')+'"]').appendTo($('.items-end[data-
items-end="'+$(this).val()+'"]')).addClass('moved');
}
});
// Interrupt the Next/Submit function and load the inputs
$('form#limesurvey').submit(function(){
$('.question-item input.text', thisQuestion).val(0);
$('.droppable.items-end .card', thisQuestion).each(function(i) {
var thisItemsEnd = $(this).closest('.droppable.items-end').attr('data-items-
end');
var thisID = $(this).attr('data-sgqa');
$('#answer'+thisID+'').val(thisItemsEnd);
});
});
}

5) Add the following to the end of template.css:


/* Card Sort */

.card-sort-question ul.subquestions-list {
/*display: none;
clear: both;*/
}

.card-sort-question .items-start {
float: left;
width: 340px;
height: 317px;
margin-top: 8px;
border: 1px solid #666;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
-khtml-border-radius: 6px;
border-radius: 6px;
}

.card-sort-question .items-start.target-hover {
background:#C9C9C9;
}

.card-sort-question .items-end-wrapper {
float: left;
margin-left: 20px;
width: 340px;
}

.card-sort-question .items-end {
width: 338px;
min-height: 73px;
margin-bottom: 10px;
padding-bottom: 5px;
border: 1px solid #666;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
-khtml-border-radius: 6px;
border-radius: 6px;
background: #EBEBEB;
}

.card-sort-question .items-end.target-hover {
background: #C9C9C9;
}

.card-sort-question .items-end.ui-state-disabled {
opacity: 1;
filter:alpha(opacity=100);
}

.card-sort-question .items-end-text {
width: 338px;
padding-bottom: 5px;
background: #FFFFFF;
font-size: 110%;
font-weight: bold;
}

.card-sort-question .card {
display: inline-block;
width: 140px;
height: auto;
margin: 5px 8px;
padding: 3px;
border: 2px solid #2F4354;
-moz-border-radius: 8px;
-webkit-border-radius: 8px;
-khtml-border-radius: 8px;
border-radius: 8px;
background-color: #43b3d1;
color: #2f4354;
font-weight: bold;
text-align: center;
line-height: normal;
}

.card-sort-question .items-end .card {


margin: 5px 7px;
}
.card-sort-question div.answer {
clear: both;
padding-top: 1px;
margin-top: 0;
}

6) Add this to the source of the question to apply the function.


<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
cardSort({QID});
});
</script>

Text Input (e.g. "short free text", "huge free text"): Force
UpperCase + onBlur Trim
Add this to the source of your target question to apply the function.
Here is the primary JavaScript/jQuery coding:

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


$( document ).ready( function() {
var this_surveyId='{SID}';
var this_groupId='{self.gid}';
var this_quesitonId='{self.qid}';
var this_self='{self.sgqa}';

// Remark for the variable 'my_targetTextBox':


// ------------------------------------
// For Text-type question (e.g. 'short free text', 'huge free text'), use : '#answer' +
this_self;
// For 'Multiple Choice'-type question enabled with the 'Other' option which provides a
textbox, use : '#answer' + this_self;
// For 'List (radio)'-type question enabled with the 'Other' option which provides a textbox,
use : '#answer' + this_self + 'text'.
var my_targetTextBox='#answer' + this_self;

// Trim:
// -----
$( my_targetTextBox ).blur( function(){
$( this ).val( $.trim( $( this ).val() ) );
});

// Uppercase:
// ----------
// Operate for its appearance only (but have no effect to modify its intrinsic value) - display
as UpperCase:
$( my_targetTextBox ).css( 'text-transform', 'uppercase' );
// Operate for its intrinsic value:
$( my_targetTextBox ).blur( function() {
$( this ).val( $( this ).val().toUpperCase() );
});
});
</script>
For Text-type question (e.g. 'short free text', 'huge free text'), set the 'this_self' variable as below:

var my_targetTextBox='#answer' + this_self;


For 'Multiple Choice'-type question enabled with the 'Other' option which provides a textbox, set the 'this_self' variable as
below:

var my_targetTextBox='#answer' + this_self;

For 'List (radio)'-type question enabled with the 'Other' option which provides a textbox, set the 'this_self' variable as
below:

var my_targetTextBox='#answer' + this_self + 'text';

Convert arrays to dropdowns on smaller screens


Tested with: 2.05, IE 8-11, Firefox, Chrome
Here's a little jQuery plugin that converts arrays to drop-downs on smaller screens.

IMPLEMENTATION:

1) Add the following to the end of template.js:


$(document).ready(function(){
// Apply the responsiveArray plugin with default settings
$('.array-10-pt, .array-flexible-row, .array-5-pt, .array-increase-same-decrease, .array-yes-
uncertain-no').responsiveArray();
});

/*****
A plugin to insert drop-downs into arrays for small screens
Copyright (C) 2015 - Tony Partner (http://partnersurveys.com)
Licensed MIT, GPL
Version - 2.0
Create date - 09/04/15
*****/
(function( $ ){
$.fn.responsiveArray = function(options) {

var opts = $.extend( {


chooseText: 'Please choose...'
}, options);

return this.each(function() {

var thisQuestion = $(this);


var thisQID = $(thisQuestion).attr('id').split('question')[1];
// Some classes
$(thisQuestion).addClass('responsive-array');
$('table.questions-list tr', thisQuestion).each(function(i){
$('> *', this).each(function(i){
$(this).addClass('col-'+i);
if(i != 0) {
$(this).addClass('expanded');
}
});
});

// Insert a column
$('.col-0', thisQuestion).after('<td class="dropdown-cell" />');

// Insert dropdowns
$('body').append('<select style="display:none;" class="responsive-select responsive-
select-'+thisQID+'" />');
$('table.questions-list thead th.expanded', thisQuestion).each(function(i){
$('.responsive-select-'+thisQID).append('<option value="">'+$(this).text()
+'</option>');
});
$('table.questions-list tbody .dropdown-cell', thisQuestion).append($('.responsive-
select-'+thisQID+'').clone());
$('tr.radio-list', thisQuestion).each(function(i){
var thisRow = $(this);
$('input.radio', this).each(function(i){
$('.responsive-select-'+thisQID+' option:eq('+i+')', thisRow).attr('value', $
(this).attr('id'));
});
if($('input.radio:checked', thisRow).length > 0) {
$('.responsive-select-'+thisQID+'', thisRow).val($('input.radio:checked',
thisRow).attr('id'));
}
else {
$('.responsive-select-'+thisQID+'', thisRow).prepend('<option value=""
selected="selected">'+opts.chooseText+'</option>');
}
});
$('.responsive-select-'+thisQID+'', thisQuestion).show();

// Listeners on radios
$('input.radio', thisQuestion).click(function(event) {
var thisRow = $(this).closest('tr');
var thisID = $(this).attr('id');
//$('option[value="'+thisID+'"]').attr('selected', 'selected');
$('.responsive-select', thisRow).val(thisID);
$('.responsive-select option[value=""]', thisRow).remove();
});
// Listeners on dropdowns
$('.responsive-select-'+thisQID+'').change(function() {
$('#'+$(this).val()+'').click();
});

});
};
})( jQuery );

2) Add something like this to template.css (I have it set to switch at 640px but adjust that as you see fit):
/*****
Styles for the responsiveArray plugin
Copyright (C) 2015 - Tony Partner (http://partnersurveys.com)
Licensed MIT, GPL
Version - 2.0
Create date - 09/04/15
*****/

.responsive-array .dropdown-cell {
display: none;
text-align: left;
padding-left: 8px;
}

.responsive-array .dropdown-cell select {


max-width: none;
}

@media screen and (max-width: 640px) {


.responsive-array table.question {
table-layout: auto;
}
.responsive-array table.question .col-0,
.responsive-array table.question .dropdown-cell {
width: 50%;
}
.responsive-array .dropdown-cell {
display: table-cell;
}
.responsive-array th.expanded,
.responsive-array td.expanded {
display: none;
}
}
Note that the "Please choose" text is an optional setting. To change it, apply the plugin like this:
$(document).ready(function(){
// Apply the responsiveArray plugin with default settings
$('.array-10-pt, .array-flexible-row, .array-5-pt, .array-increase-same-decrease, .array-yes-
uncertain-no').responsiveArray({
chooseText: 'Select one...'
});
});

Van Westendorp Pricing Sliders

Version 2.06
Tested with: LimeSurvey version 2.06
This workaround uses JavaScript to dynamically control sliders to facilitate the Van Westendorp pricing research
methodology. When a slider is manipulated, all of the "higher" sliders are dynamically pushed to higher levels and "lower"
sliders are pushed to lower levels. This ensures that the data will always be valid.
Download a working template and survey (un-zip the package and install the template before the survey).
Implementation:
1. Set up your survey to use JavaScript.
2. Create your Multiple Numeric question with sub-questions as seen in the image above.
3. Add the following to the end of template.js:
4. function shuffleArray(array) {

5. for (var i = array.length - 1; i > 0; i--) {

6. var j = Math.floor(Math.random() * (i + 1));


7. var temp = array[i];

8. array[i] = array[j];

9. array[j] = temp;

10. }

11. return array;

12. }

13.

14.

15. /*****

16. A jQuery plugin to facilitate the Van Westendorp pricing research methodology

17. Copyright (C) 2016 - Tony Partner (http://partnersurveys.com)

18. Licensed MIT, GPL

19. Version - 2.0

20. Create date - 19/10/16

21. *****/

22. (function( $ ){

23.

24. $.fn.vWPricing = function(options) {

25.

26. var opts = $.extend( {

27. order: [1, 2, 3, 4],

28. randomOrder: false

29. }, options);

30.
31. return this.each(function() {

32.

33. var thisQuestion = $(this);

34.

35. thisQuestion.addClass('vwp-question');

36.

37. // Add some attributes and set the slider max/min values

38. var itemsLength = $('li.question-item', thisQuestion).length;

39. $('li.question-item', thisQuestion).each(function(i) {

40. $(this).attr('data-item', (i+1));

41. $('.ui-slider', this).attr('data-slider', (i+1));

42.

43. var thisSliderMin = $('.ui-slider', this).slider('option', 'min');

44. $('.ui-slider', this).slider('option', 'min', thisSliderMin+(i));

45. var thisSliderMax = $('.ui-slider', this).slider('option', 'max');

46. $('.ui-slider', this).slider('option', 'max', thisSliderMax-((itemsLength-1)-i));

47. });

48.

49. // Slider order

50. if(opts.randomOrder == true) {

51. shuffleArray(opts.order);

52. }

53. $(opts.order).each(function(i, val) {


54. $('ul.subquestions-list', thisQuestion).append($('li.question-item[data-

item="'+val+'"]', thisQuestion));

55. });

56.

57. // Listeners on the sliders

58. $('.ui-slider', thisQuestion).on('slide', function(event, ui) {

59. handleVwpSliders(this, ui.value);

60. });

61. $('.ui-slider', thisQuestion).on('slidestop', function(event, ui) {

62. handleVwpSliders(this, ui.value);

63. });

64.

65. // A function to handle the siders

66. function handleVwpSliders(el, sliderVal) {

67. var thisRow = $(el).closest('li');

68. var movedIndexNum = $(el).attr('data-slider');

69.

70. $('input.text', thisRow).val(sliderVal);

71. $('.slider-callout', thisRow).html($('input.text', thisRow).val());

72.

73. var lowerSliders = $('.ui-slider', thisQuestion).filter(function() {

74. return $(this).attr('data-slider') < movedIndexNum;

75. });

76. var higherSliders = $('.ui-slider', thisQuestion).filter(function() {


77. return $(this).attr('data-slider') > movedIndexNum;

78. });

79.

80. $(lowerSliders).each(function(i) {

81. var thisIndexNum = $(this).attr('data-slider');

82. var newSliderVal = sliderVal-(movedIndexNum-thisIndexNum);

83. if($(this).slider('option', 'value') > newSliderVal) {

84. var thisRow = $(this).closest('li');

85. $(this).slider('option', 'value', newSliderVal);

86. $('input.text', thisRow).val(newSliderVal);

87. $('.slider-callout', thisRow).html($('input.text', thisRow).val());

88. }

89. });

90. $(higherSliders).each(function(i) {

91. var thisIndexNum = $(this).attr('data-slider');

92. var newSliderVal = sliderVal+(thisIndexNum-movedIndexNum);

93. if($(this).slider('option', 'value') < newSliderVal) {

94. var thisRow = $(this).closest('li');

95. $(this).slider('option', 'value', newSliderVal);

96. $('input.text', thisRow).val(newSliderVal);

97. $('.slider-callout', thisRow).html($('input.text', thisRow).val());

98. }

99. });

100. }
101. });

102.

103. };

104. })( jQuery );

105. Add the following to the end of template.css (this is for a copy of Denis Chenu's SkeletonQuest template):
106. /**** Van Westendorp Pricing ****/

107.

108. .vwp-question ul.subquestions-list,

109. .vwp-question ul.subquestions-list li {

110. display:block;

111. }

112.

113. .vwp-question ul.subquestions-list li {

114. margin-bottom: 15px;

115. }

116.

117. .vwp-question ul.subquestions-list .slider-label {

118. display:block;

119. padding: 0.3em 0.5em 0.7em;

120. width: auto;

121. max-width: 100%;

122. text-align: left;

123. }

124.
125. .vwp-question .slider-element {

126. display: block;

127. margin-left: 2%;

128. width:90%;

129. max-width: 32em;

130. }

131. To apply the plugin, add this script the multiple-numeric question source.
There are two options to adjust
-order - this is the order that your sub-questions will be displayed (default, 1, 2, 3, 4)
-randomOrder - if set to true, the order option will be overridden and the sub-question order will be randomized
(default, false)
<script type="text/javascript" charset="utf-8">
$(document).ready(function(){

$('#question{QID}').vWPricing({
order: [1, 2, 3, 4],
randomOrder: false
});

});
</script>

Version 2.5x
Tested with: LimeSurvey version 2.54.5
This workaround uses JavaScript to dynamically control sliders to facilitate the Van Westendorp pricing research
methodology. When a slider is manipulated, all of the "higher" sliders are dynamically pushed to higher levels and "lower"
sliders are pushed to lower levels. This ensures that the data will always be valid.

Download a working template and survey (un-zip the package and install the template before the survey).
Implementation:
1. Set up your survey to use JavaScript.
2. Create your Multiple Numeric question with sub-questions as seen in the image above.
3. Add the following to the end of template.js:
4. function shuffleArray(array) {

5. for (var i = array.length - 1; i > 0; i--) {

6. var j = Math.floor(Math.random() * (i + 1));

7. var temp = array[i];

8. array[i] = array[j];

9. array[j] = temp;

10. }

11. return array;

12. }

13.

14.

15. /*****

16. A jQuery plugin to facilitate the Van Westendorp pricing research methodology

17. Copyright (C) 2016 - Tony Partner (http://partnersurveys.com)

18. Licensed MIT, GPL

19. Version - 2.0

20. Create date - 19/10/16

21. *****/

22. (function( $ ){

23.

24. $.fn.vWPricing25 = function(options) {

25.

26. var opts = $.extend( {


27. order: [1, 2, 3, 4],

28. randomOrder: false

29. }, options);

30.

31. return this.each(function() {

32.

33. var thisQuestion = $(this);

34.

35. thisQuestion.addClass('vwp-question');

36.

37. // Add some attributes and set the slider max/min values

38. var itemsLength = $('.question-item', thisQuestion).length;

39. $('.question-item', thisQuestion).each(function(i) {

40. var thisInput = $('input[type=text]', this);

41. var thisVal = $(thisInput).val();

42.

43. $(this).attr('data-item', (i+1));

44. $(thisInput).attr('data-slider-index', (i+1));

45.

46. // Slider initial settings

47. setTimeout(function() {

48. var thisSliderMin = $(thisInput).bootstrapSlider('getAttribute', 'min');

49. $(thisInput).bootstrapSlider('setAttribute', 'min', thisSliderMin+(i));

50. var thisSliderMax = $(thisInput).bootstrapSlider('getAttribute', 'max');


51. $(thisInput).bootstrapSlider('setAttribute', 'max', thisSliderMax-

((itemsLength-1)-i));

52.

53. if(thisVal == '') {

54. $(thisInput).val('');

55. }

56. }, 200);

57.

58. });

59.

60. // Slider order

61. if(opts.randomOrder == true) {

62. shuffleArray(opts.order);

63. }

64. $(opts.order).each(function(i, val) {

65. $('.subquestion-list.questions-list', thisQuestion).append($('.question-item[data-

item="'+val+'"]', thisQuestion));

66. });

67.

68. // Listeners on the sliders

69. $('input[type=text]', thisQuestion).on('slide', function(event) {

70. handleVwpSliders(this, $(this).bootstrapSlider('getValue'));

71. });

72. $('input[type=text]', thisQuestion).on('slideStop', function(event) {


73. handleVwpSliders(this, $(this).bootstrapSlider('getValue'));

74. });

75.

76. // A function to handle the siders

77. function handleVwpSliders(el, sliderVal) {

78. var movedIndexNum = $(el).attr('data-slider-index');

79.

80. var lowerSliders = $('input[type=text]', thisQuestion).filter(function() {

81. return $(this).attr('data-slider-index') < movedIndexNum;

82. });

83. var higherSliders = $('input[type=text]', thisQuestion).filter(function() {

84. return $(this).attr('data-slider-index') > movedIndexNum;

85. });

86.

87. $(lowerSliders).each(function(i) {

88. var thisIndexNum = $(this).attr('data-slider-index');

89. var newSliderVal = sliderVal-(movedIndexNum-thisIndexNum);

90. if($(this).bootstrapSlider('getValue') > newSliderVal) {

91. var thisRow = $(this).closest('.question-item');

92. var thisTooltip = $('.tooltip', thisRow);

93. $(this).bootstrapSlider('setValue', newSliderVal);

94. $('.tooltip-inner', thisTooltip).text($('input.text',

thisRow).val());
95. thisTooltip.show().css('margin-left', '-'+

(thisTooltip.width()/2)+'px');

96. }

97. });

98. $(higherSliders).each(function(i) {

99. var thisIndexNum = $(this).attr('data-slider-index');

100. var newSliderVal = sliderVal+(thisIndexNum-movedIndexNum);

101. if($(this).bootstrapSlider('getValue') < newSliderVal) {

102. var thisRow = $(this).closest('.question-item');

103. var thisTooltip = $('.tooltip', thisRow);

104. $(this).bootstrapSlider('setValue', newSliderVal);

105. $('.tooltip-inner', thisTooltip).text($('input.text',

thisRow).val());

106. thisTooltip.show().css('margin-left', '-'+

(thisTooltip.width()/2)+'px');

107. }

108. });

109. }

110.

111. // Listener on resizing (override the bootstrap callout behaviour)

112. $(window).resize(function() {

113. setTimeout(function() {

114. $('input[type=text]', thisQuestion).each(function(i) {

115. if($(this).val() != '') {


116. var thisRow = $(this).closest('.question-item');

117. var thisTooltip = $('.tooltip', thisRow);

118. $('.tooltip-inner', thisTooltip).text($.trim($

(this).val()));

119. console.log($('.tooltip-inner',

thisTooltip).text());

120. thisTooltip.show().css('margin-left', '-'+

(thisTooltip.width()/2)+'px');

121. }

122. });

123. }, 1);

124. });

125. });

126. };

127. })( jQuery );

128. Add the following to the end of template.css (this is for a copy of the default template):
129. /*********** Van Westendorp Pricing ***********/

130.

131. .vwp-question .control-label {

132. margin-bottom: 35px;

133. }

134.

135. .vwp-question .withslider {

136. margin-bottom: 0px;


137. }

138.

139. .vwp-question .slider-container {

140. margin-bottom: 2.5em;

141. margin-top: 3.5em;

142. }

143.

144. .vwp-question .slider.slider-horizontal .slider-handle {

145. margin-top: -5px;

146. }

147.

148. @media (max-width: 600px){

149.

150. .vwp-question .withslider {

151. padding-left: 0;

152. padding-right: 0;

153. }

154. }

155.

156. @media (max-width: 480px){

157.

158. #outerframeContainer,

159. .vwp-question .control-label,

160. .vwp-question .slider-container > div {


161. padding-left: 0;

162. padding-right: 0;

163. }

164.

165. .vwp-question .slider-container > div > div {

166. padding-left: 5px;

167. padding-right: 5px;

168. }

169. }

170. To apply the plugin, add this script the multiple-numeric question source.
There are two options to adjust
-order - this is the order that your sub-questions will be displayed (default, 1, 2, 3, 4)
-randomOrder - if set to true, the order option will be overridden and the sub-question order will be randomized
(default, false)
<script type="text/javascript" charset="utf-8">
$(document).ready(function(){

$('#question{QID}').vWPricing25({
order: [1, 2, 3, 4],
randomOrder: false
});

});
</script>
This page was last modified on 30 May 2017, at 15:46.
Content is available under Creative Commons by SA unless otherwise noted.
Privacy policy
About LimeSurvey Manual
Disclaimers
Powered by MediaWiki

LimeSurvey Manual
Navigation
o Main page
o Recent changes
o Random page
o Help

Search

o
What links here
o
Related changes
o
Special pages
o
Printable version
o
Permanent link
o
Page information
o
Recent changes
o
Help

o
English
o
Log in
o Register
Actions

Optional settings

This page contains changes which are not marked for translation.

Other languages:
Deutsch English espaol franais italiano Nederlands portugus slovenina Trke

Contents
[hide]
1 How to modify optional settings
2 Yii settings
o 2.1 Database settings
o 2.2 Session settings
o 2.3 Request settings
o 2.4 URL settings
o 2.5 Logging settings
o 2.6 Runtime Path
3 General settings
4 Security
5 Resources
6 Appearance
7 Language & time
8 Survey behavior
9 Development and debugging
10 Email Settings
11 Statistics and Response Browsing
12 LDAP settings
13 Authentication
o 13.1 Delegate Authentication to the Webserver
o 13.2 Authentication Delegation with no automatic user import
o 13.3 Authentication Delegation with automatic user import
o 13.4 User name mapping
o 13.5 Use one time passwords
14 Advanced Path Settings

How to modify optional settings


The optional settings can only be found in the file /application/config/config-defaults.php of a standard installation - some
of them are only used for first installation, some of them overriden in Global settings.
If you want to change these settings please do not change it in config-defaults.php but copy the particular setting/line
over to /application/config/config.php in 'config'=>array() and change it there.
All settings in config.php overwrite the default values from config-defaults.php and some of these settings get overridden
in the Global settings dialog (New in 1.87 ) . This way it is much easier to upgrade your installation at a later time!
To update/add the LimeSurvey settings in /application/config/config.php you have to update the config array:

'config'=>array(
'debug'=>0,
'debugsql'=>0,
'LimeSurveySetting'=>'New value',
)

Yii settings
LimeSurvey uses the Yii framework and Yii has its own configuration parameters in the application/config/config.php file.
You can access some specific configuration settings of LimeSurvey also via the Yii configuration.
The Yii-specific settings are set in the components array:

'components' => array(


'db' => array(
....
),
'Specific settings'=>array(
....
),
),
Database settings
The database settings are written by the installer to the config.php file when you install LimeSurvey for the first time. If
needed you can update this part of the config. Please remember that you do this at your own risk. See also the [Yii
documentation], and please remember that LimeSurvey supports only the database types mysql,pgsql,dblib,mssql, and
sqlsrv.

Session settings
You can set some session parameters in config.php, the first example is the session in the database. You can
uncomment/add the part needed in config.php. See [Yii Documentation] for other settings.
If you use SSL ('https') for your LimeSurvey installation adding the following lines to your config.php will increase session
security:

// Set the cookie via SSL


'session' => array (
'cookieParams' => array(
'secure' => true, // use SSL for cookies
'httponly' => true // Cookies may not be used by other protocols - experimental
),
),
If you want to fix the domain for a cookie use this in config.php:

// Set the domain for cookie


'session' => array (
'cookieParams' => array(
'domain' => '.example.org',
),
),

Request settings
The request settings are important, but the default settings are already optimized for LimeSurvey usage. See Yii
Documentationfor more information.
Some example you can modify in your LimeSurvey configuration:At your own risk :

// Disable CSRF protection


'request' => array(
'enableCsrfValidation'=>false,
),

// Enforce a certain base URL


'request' => array(
'hostInfo' => 'http://www.example.org/'
),

// Set the cookie domain name for CSRF protection


'request' => array(
'csrfCookie' => array( 'domain' => '.example.com' )
),
If you need updating only the url for token email : you can set your publicurl in your config.php file.

URL settings
To have the same behaviour like the old 'Fancy URL' feature , you can update the urlManager
// Use short URL
'urlManager' => array(
'urlFormat' => 'path',
'showScriptName' => false,
),
Adding .html after the survey id

// Use short URL


'urlManager' => array(
'urlFormat' => 'path',
'rules' => array (
'<sid:\d+>' => array('survey/index','urlSuffix'=>'.html','matchValue'=>true),
),
'showScriptName' => false,
),
Find more information in the Yii documentation.

Logging settings
Yii have a lot of solution to generate log, you can check Special Topics: Logging . LimeSurvey use it with config=1 or 2, but
display to any web user. You can use your own system using Yii directly.
For example a quick solution to log error and warning in a files

// Log error
'log' => array(
'routes' => array(
'fileError' => array(
'class' => 'CFileLogRoute',
'levels' => 'warning, error',
),
),
),

Hint: By default the file is saved in limesurvey/tmp/runtime/application.log .

Yii use runtimePath, and the default one in limesurvey are web accessible. Log file can contains a lot of information from
your server. Best is to use a directory out of web accessible. You can set it in routes, or update runtimePath.

Runtime Path
Runtime path must be a directory readable and writable by the web user. Runtime path contain file with potential
security information, this path can be out of public web access. By default, LimeSurvey use a directory in temp directory,
inside web access. You can set runtime path out of web access before LimeSurve default settings using config.php.

return array(
'components' => array(
[]
'runtimePath'=>'/var/limesurvey/runtime/',
'config'=>array(
[]
)
)
)

General settings
sitename: Give your survey site a name. This name will appear in the survey list overview and in the
administration header. This setting is used only as default value and overridden in the Global settings dialog (New in 1.87 ).
siteadminemail: This is the default email address of the site administrator and used for system messages and
contact options. This setting is used only as default value and overridden by the Global settings dialog (New in 1.87 ).
siteadminbounce: This is the email address where bounced emails will be sent to. This setting is used only as
default value and overridden by the Global settings dialog (New in 1.87 ).
siteadminname: The real name of the site administrator. This setting is used only as default value and
overridden in theGlobal settings dialog) (New in 1.87 ).
proxy_host_name: This is the host name of your proxy server (if you are behind a proxy and want to update
LimeSurvey using ComfortUpdate) (New in 2.05 ).
proxy_host_port: This is the port of your proxy server (if you are behind a proxy and want to update
LimeSurvey using ComfortUpdate) (New in 2.05 ).

Security
maxLoginAttempt: If the user enters password incorrectly this is the number of attempts before the users is
locked out by IP address.
timeOutTime: If the user enters password incorrectly for <maxLoginAttempt> set the lock out time (in
seconds).
surveyPreview_require_Auth: true by default. If you set this to false any person can test your survey using
the survey URL - without logging in to the administration and without having to activate the survey first. This setting
is a default value and can be overridden in the Global settings dialog (New in 1.87 ).
usercontrolSameGroupPolicy : Set to true by default. By default non-admin users defined in the LimeSurvey
management interface will only be able to see users they create or users that belongs to at least one same group.
This setting is a default value and can be overridden in the Global settings dialog.
filterxsshtml: This setting enables filtering of suspicious html tags in survey, group, questions and answer texts
in the administration interface. Only leave this to 'false' if you absolutely trust the users you created for the
administration of LimeSurvey and if you want to allow these users to be able to use Javascript, Flash Movies,
etc. Super admin never have their HTML filtered. This setting is a default value and can be overridden in the Global
settings dialog (New in 1.87 ).
demoModeOnly: If this option is set to true, then LimeSurvey will go into demo mode. The demo mode
changes the following things:
o Disables changing of the admin user's details and password
o Disables uploading files on the template editor
o Disables sending email invitations and reminders
o Disables doing a database dump
o Disables the ability to modify the following global settings: Site name, Default language, Default
Htmleditor Mode, XSS filter

Resources
sessionlifetime: Defines the time in seconds after which a survey session expires. It applies only if you are
using database sessions. This setting is overridden in the Global settings dialog) (New in 1.87 ).
memorylimit: This sets how much memory LimeSurvey can access. '128M' is the minimum (M=Megabyte)
recommended. If you receive time out errors or have problems generating statistics or exporting files raise this limit
to '256M' or higher. If your webserver has set a higher limit then this setting will be ignored.
Please mind that such local settings by an application can always be overruled by global server settings.
To increase the memory limit to 128M you could also try adding:
memory_limit = 128M to your server's main php.ini file (recommended, if you have access)
memory_limit = 128M to a php.ini file in the LimeSurvey root
php_value memory_limit 128M in a .htaccess file in the LimeSurvey root

Appearance
lwcdropdowns: (Obsolete since 2.0) This can be set to either "L" or "R". Setting it to "R" will result in 'List with Comment'
questions being displayed as radio buttons, whereas "L" will result in 'List with Comment' questions being displayed
in a 'dropdown' list box .
dropdownthreshold:' (Obsolete since 2.50) When you have selected "R" for $dropdowns, this allows you to set a
maximum number of options that will display as radio buttons, before converting back to a dropdown list. If you
have a question that has a large number of options, displaying them all as radio buttons can look unweildy, and be
counter-intuitive to users. Setting this to a maximum of, say 25 (which is the default) means that large lists are
easier for the survey participant to use.
repeatheadings: With the Array type question, often you'll have a lot of subquestions, which - when displayed
on screen - take up more than one page. This setting lets you decide how many subquestions should be displayed
before repeating the header information for the question. A good setting for this is around 15. If you don't want the
headings to repeat at all, set this to 0. This setting is overridden in the Global settings dialog) (New in 2.05 ).
minrepeatheadings: The minimum number of remaining subquestions that are required before repeating the
headings in Array questions.
defaulttemplate: This setting specifys the default theme used for the 'public list' of surveys. This setting is
overridden in the Global settings dialog) (New in 1.87 ).
defaulthtmleditormode: Sets the default mode for integrated HTML editor. This setting is overridden in
the Global settingsdialog) (New in 1.87 ). Valid settings are:
o inline: Inline replacement of fields by an HTML editor. Slow but convenient and user friendly
o popup: Adds an icon that runs the HTML editor in a popup if needed. Faster, but HTML code is
displayed in the form.
o none: No HTML editor
column_style: Defines how columns are rendered for survey answers when using display_columns. Valid
settings are:
o 'css' using one of the various CSS only methods for creating columns (see template style sheet for
details).
o 'ul' using multiple floated unordered lists. (default)
o 'table' using conventional tables based layout.
o NULL disable the use of columns

Language & time


defaultlang: This should be set to the default language to be used in your administration scripts, and also the
default setting for language in the public survey list. This setting is overridden in the Global settings dialog) (New in 1.87 ).
timeadjust: If your web server is in a different time zone to the location where your surveys will be based, put
the difference between your server and your home time zone here. For example, I live in Australia but use a US web
server. The web server is 14 hours behind my local time zone. So my setting here is "14". In other words, it adds 14
hours to the web servers time. This setting is particularly important when surveys timestamp the responses. This
setting is overridden in theGlobal settings dialog) (New in 1.87 ).
Survey behavior
deletenonvalues: Use this feature with caution. By default (a value of 1), irrelevant questions are NULLed in
the database. This ensures that the data in your database is internally consistent. However, there are rare cases
where you might want to hold onto irrelevant values, in which case you can set the value to 0. Say you ask the
person his gender, and he accidentally says 'female' and then answers some female-specific questions (questions
that are conditioned on being female, so are only relevant for women). Then, he realizes his mistake, backs up, sets
the gender to 'male', and continues with the survey. Now, the female-specific questions are irrelevant. If
$deletenonvalues==1, those irrelevant values will be cleared (NULLed) in the database. If $deletenonvalues==0,
his erroneous answers will not be deleted, so they will still be present in the database when you analyze it.
shownoanswer: When a question of a radio button/select type that contains editable answers (ie: List, Array
questions) is not mandatory and shownoanswer is set to 1, an additional 'No answer' entry is shown - so that
participants may choose to not answer the question. Some people prefer this not to be available. This setting is
overridden in the Global settings dialog)(New in 1.87 ). Valid values are:
o 0 = no ,
o 1 = yes ,
o 2 = survey admin can choose.
printanswershonorsconditions: This setting determines if the printanswers feature will display entries from
questions that were hidden by conditions-branching (Default: 1 = hide answers from questions hidden by
conditions).
hide_groupdescr_allinone: (New in 1.85 ) This setting is relevant for all-in-one surveys using conditions . When this is
set to true the group name and description is hidden if all questions in the group are hidden. (Default: true - hide
group name and description when all questions in the group are hidden by conditions)
showpopups: Show popup messages if mandatory or conditional questions have not been answered correctly.
1=Show popup message (default), 0=Show message on page instead, -1=Do not show the message at all (in this
case, users will still see the question-specific tips indicating which questions must be answered).

Development and debugging


debug: With this setting you set the PHP error reporting to E_ALL. That means every little notice, warning or
error with the script is shown. This setting should be only switched to 1 if you are trying to debug the application for
any reason, if you are a developer switch it to 2. Don't switch it to 1 or 2 in production since it might cause path
disclosure. (Default: 0)
debugsql: Activate this setting if you want to display all SQL queries executed for the script on the bottom of
each page. Very useful for optimizing the number of queries
If you experience an error in the application, we strongly recommend to acivate the debug setting so you usually get
some more detailed error that you can submit with the bug report:

'config'=>array(
'debug'=>2,
'debugsql'=>0,
)

Email Settings
Note: All these settings in this section are overridden in the Global settings dialog (New in 1.87 ).
emailmethod: This determines how E-mail messages are being sent. The following options are available:
o mail: use internal PHP mailer
o sendmail: use sendmail mailer
o smtp:use SMTP relaying. Use this setting when you are running LimeSurvey on a host that is not your
mail server.
emailsmtphost: If you use 'smtp' as $emailmethod then you have to put your SMTP-server here. If you are
using Google mail you might have to add the port number like $emailsmtphost = 'smtp.gmail.com:465';
emailsmtpuser: If your SMTP-server needs authentication then set this to your user name, otherwise it must be
blank.
emailsmtppassword: If your SMTP-server needs authentication then set this to your password, otherwise it
must be blank.
emailsmtpssl: Set this to 'ssl' or 'tls' to use SSL/TLS for SMTP connection
maxemails: When sending invitations or reminders to survey participants, this setting is used to determine how
many emails can be sent in one bunch. Different web servers have different email capacities, and if your script takes
too long to send a bunch of emails, the script could time out and cause errors. Most web servers can send 100
emails at a time within the default 30 second time limit for a PHP script. If you get script timeout errors when
sending large numbers of emails, reduce the number in this setting. Clicking the 'send email invitation' button on
the token control toolbar, (not the button on the right of each token), sends the maxemails number of invitations,
then displays a list of the addresses sent to and a warning that "There are more emails pending than could be sent
in one batch. Continue sending emails by clicking below. There are ### emails still to be sent." and provides a
"continue button" to proceed with the next batch. I.e., the user determines when to send the next batch after each
batch gets emailed. It is not necessary to wait with this screen active. The admin could log off and come back at a
later time to send the next batch of invites.

Statistics and Response Browsing


usejpgraph: (Obsolete since 1.8) The JPGraph-library lets you display the results of your survey in the statistics part of
LimeSurvey in bar- and pie charts. If you have a correctly configured jpgraph class set up on your server, you can
turn this feature on or off (1=on, 0=off). Please have a look, every version of PHP needs another version of JPGraph!
This feature is currently in development, so expect a few weird outcomes.
jpgraphdir: (Obsolete since 1.8) The physical disk location of the jpgraph class scripts. This setting is only required if
$usejpgraph is equal to 1.
jpgraphfont: (Obsolete since 1.8) The font to use with graphs. A failsafe setting would be "FF_FONT1"
embedded: (Obsolete since 2.0) If you want to integrate LimeSurvey into another page then you can turn off sending
HTML headers by using this setting and point instead to the header method of a custom function. This is a dirty hack
but should work for a quick integration.
filterout_incomplete_answers: Control the default behaviour of filtering incomplete answers when browsing
or analyzing responses. For a discussion on incomplete Responses see Browsing survey results. Since these records
can corrupt the statistics, an option is given to switch this filter on or off in several GUI forms. This parameter
config.php is just the default state for the incomplete answer filter. The following options are available:
o show: Show both complete and incomplete answers
o filter: Show only complete answers
o incomplete: Show only incomplete answers
strip_query_from_referer_url: This setting determine if the referrer URL saves parameter or not. Default value
is "false" (in this case referrer URL saves all parameter). Alternatively this value can be set to "true" and the
parameter part of the referrer URL will be removed.
showaggregateddata: (New in 1.8 ) When activated there are additional values like arithmetic mean and standard
deviation at statistics. Furthermore data is aggregated to get a faster overview e.g. results of scale 1+2 and 4+5 are
added to have a general ranking like "good" (1/2), "average" (3) and "bad" (4/5). This only affects question types "A"
(5 point array) and "5" (5 point choice).
PDF Export Settings: (New in 1.85 ) This feature activates PDF export for printable survey and Print Answers. The
PDF export is totally experimental. The output is mostly ugly. At this point no support can be given - if you want to
help to fix it please get in touch with us.
o usepdfexport: Set 0 to disable; 1 to enable
o pdfdefaultfont: Default font for the pdf Export
o alternatepdffontfile: an array with key for language and specific font for this language can be replaced
or just updated .
o pdffontsize: Fontsize for normal text; Surveytitle is +4; grouptitle is +2
o notsupportlanguages = array('zh-Hant-TW','zh-Hant-HK','zh-Hans','ja','th');
o pdforientation: Set L for Landscape or P for portrait format
'"Graph setting"'
o chartfontfile : Font file to be used : must be in the server font directory or ./fonts directory
o alternatechartfontfile : an array with key for language and specific font for this language can be
replaced or just updated .
showsgqacode: (New in 1.91 ) This setting is used at the printable survey feature and defaults to false. If you
setshowsgqacode = true; the IDs for each question - and answer if applicable - will be shown; these IDs match the
column heading at the Lime_survey_12345 table which holds the answer data for a certain survey. These Ids can be
used for a code book for manual database queries.

LDAP settings
As this is an extensive topic we have moved LDAP settings to another page.

Authentication
Starting with LimeSurvey 2.05 authentication will be handled by plugins. Because of this the information below might be
outdated. See Plugins for most up to date information.

Delegate Authentication to the Webserver


System Administrators may want to have their survey administrators authenticated against a central authentication
system (Active Directory, openLdap, Radius, ...) rather than using the internal LimeSurvey database. An easy way to do
this is to setup your Webserver software to use this external authentication system, and then ask LimeSurvey to trust the
user identity reported by the webserver. In order to enable this feature you have to:
set auth_webserver to true in config.php
enable authentication at the webserver side
Please note that:
LimeSurvey will then bypass its own authentication process (by using the login name reported by the webserver
without asking for a password)
this can only replace the LimeSurvey GUI authentication system, not the survey invitation
system (participant interface)

Authentication Delegation with no automatic user import


Please note however than by default Authentication Delegation doesn't bypass the LimeSurvey authorization system: this
means that, even if you don't have to manage passwords in LimeSurvey, you still need to define the users in the
LimeSurvey database and assign them the correct set of rights in order to let them access survey resources.
A user is then granted access to LimeSurvey if and only if:
he has been authenticated to the webserver
his login name is defined as a user in the LimeSurvey user database (the user is then granted the privileges of
the user defined in the LimeSurvey user database).

Authentication Delegation with automatic user import


When managing huge user database, it is sometimes easier to auto-import user in the LimeSurvey database.
auth_webserver_autocreate_user: if set to true LimeSurvey will try to autoimport users authenticated by the
webserver but not already in its users' DB
auth_webserver_autocreate_profile: an array describing the default profile that will be assigned to the user
(including fake First and Last name, email, privileges)
If you want to customize the user profile so that it matches the logged-in user, you'll have to develop a simple function
calledhook_get_autouserprofile: with this function you can retrieve from a central User account database (for instance
an LDAP directory), the true First&Last; names and email of a particular user. You can even customize his privileges on the
system based on his groups on the external DB.
The hook_get_auth_webserver_profile function takes the user login name as the only argument and can return:
False or an empty array: in this case the user is denied access to LimeSurvey
an array containing all common userprofile entries as described in the $WebserverAuth_autouserprofile
function hook_get_auth_webserver_profile($user_name)
{
// Retrieve user's data from your database backend (for instance LDAP) here
... get $user_name_from_backend
... get $user_email_from_backend
... get $user_lang_from_backend
... from groups defined in your backend set $user_admin_status_frombackend_0_or_1
return Array(
'full_name' => "$user_name_from_backend",
'email' => "$user_email_from_backend",
'lang' => '$user_lang_from_backend',
'htmleditormode' => 'inline',
'templatelist' => 'default,basic,MyOrgTemplate',
'create_survey' => 1,
'create_user' => 0,
'delete_user' => 0,
'superadmin' => $user_admin_status_frombackend_0_or_1,
'configurator' =>0,
'manage_template' => 0,
'manage_label' => 0);
}

// If user should be denied access, return an empty array

// return Array();
User name mapping
In case some users have an external user name that is different from their LimeSurvey user name, you may find useful to
use a user name mapping. This is done in LimeSurvey by using the auth_webserver_user_map parameter. For instance
imagine you don't have an 'admin' username defined in your external authentication database. Then in order to login to
LimeSurvey as admin, you'll have to map your external username let's call it 'myname' to the admin login name in
LimeSurvey. The corresponding setup is :

'config'=>array(
...
'auth_webserver_user_map' => array ('myname' => 'admin');
)
After a successful authentication with the 'myname' login and password to the webserver, you'll be directly authorized to
use LimeSurvey as the 'admin' user.
This obviously has serious security implications, so use it with care, and please protect your config.php from write access
by the web server.

Use one time passwords


A user can open the LimeSurvey login at /limesurvey/admin and pass username and a one time password which was
previously written into the users table (column one_time_pw) by an external application.
This setting has to be turned on (use_one_time_passwords => true) to enable the usage of one time passwords
(default = false). More information can be found at "Manage Users".

Advanced Path Settings


homeurl: This should be set to the URL location of your administration scripts. These are the scripts in the
/limesurvey/admin folder. This should be set to the WEB URL location - for example
"http://www.example.com/limesurvey/html/admin". Don't add a trailing slash to this entry. The default setting in
config.php attempts to detect the name of your server automatically using a php variable setting -
{$_SERVER['SERVER_NAME']}. In most cases you can leave this and just modify the remainder of this string to
match the directory name you have put the LimeSurvey scripts in.
publicurl: This should be set to the URL location of your 'public scripts'. The public scripts are those located in
the "limesurvey" folder (or whatever name you gave to the directory that all the other scripts and directories are
kept in). This settings is available in config.php and is used when sending token email.
tempurl: This should be set to the URL location of your "/limesurvey/tmp" directory - or a directory which you
would like LimeSurvey to use to store temporary files, including uploads. This directory must be set to read & write
for your webserver (e.g. chmod 755).
imagefiles: By default you should leave this pointing to the URL location of /limesurvey/admin/images - where
the images are installed initially. You may, however, prefer to move these images to another location and if so point
this to the URL directory where they are stored.
homedir: This should be set to the physical disk location of your administration scripts - for example
"/home/usr/htdocs/limesurvey/admin". Don't add a trailing slash to this entry. The default setting in config.php
attempts to detect the default root path of all your documents using the php variable setting -
{$_SERVER['DOCUMENT_ROOT']}. In most cases you can leave this and just modify the remainder of this string to
match the directory name you have put the LimeSurvey scripts in.
publicdir: This should be set to the physical disk location of your 'public scripts'.
tempdir: This should be set to the physical disk location of your /limesurvey/tmp directory so that the script can
read and write files.
sCKEditorURL: url of the fckeditor script
fckeditexpandtoolbar: defines if the FCKeditor toolbar should be opened by default
pdfexportdir: Directory with the tcpdf.php extensiontcpdf.php
pdffonts: Directory for the TCPDF fonts
This page was last modified on 20 June 2017, at 09:36.
Content is available under Creative Commons by SA unless otherwise noted.
Privacy policy
About LimeSurvey Manual
Disclaimers
Powered by MediaWiki