Introduction¶
dotData provides a comprehensive system to roll your own question types using the standardized question object model. The set of pre-rolled question types using this architecture is using jQuery but with the same mechanism you can make question types using any other JavaScript library.
What is needed for setting this up is some knowledge about JSON, HTML and JavaScript.
Note
We’ve built for this purpose a question type in 50 lines of JavaScript code, including comments and white lines that is described in this topic.
The code from this example is available in git: https://github.com/Lexcon/rollYourOwnDemo.git
A ‘rolled your own’ question type typically consists of three elements: the HTML fragment, JavasScript and a CSS file.
The HTML fragment¶
This is a text file that contains the basic layout of your question type. Typically there are one or more containers that are <div> element. Within this element, your JavaScript will draw the actual controls, and where the user interaction takes place.
Also in this file are references to external files and script. Typically (but not necessarily) links to jQuery and other libraries are included here.
This fragment lives in a file that you name, preferably with a descriptive name, eg myQuestionType.txt
Example: myQuestiontype.txt
1[# insertonce(__jquery__) #]
2[# insertonce(__dd__) #]
3[# insertonce('<script type="text/javascript" src="' + __packagehome__ + '/js/myQuestionType.js"></script>') #]
4[# insertonce('<link rel="stylesheet" href="' + __packagehome__ + '/css/myQuestionType.css"/>') #]
5
6<div id="[# questions.varlabel #]-wrapper"></div>
7
8<script type="text/javascript">
9 $(document).ready(function() {
10 $(document).myQuestionType([# __item__ #]);
11 });
12</script>
- Remarks per line:
line 1: note the insertonce() directive. Should you have multiple items on one page that share the same libary, the insertonce() directive will make sure that a specific inclusion of a library or snippet happens only once
line 1: note the __jquery__ constant, which translates to a fully qualified inclusion of the latest jQuery library. Of course you can also add a hardcoded path to jQuery but by using these constants you can always be sure the latest jQuery is used
line 2: the __dd__ directive inserts a standard dotData javascript library
line 3: note that the resources you provide live in /resources/myaccount, where myaccount needs to be substituted by your user name. To have the system insert this path, use the __packagehome__ variable.
Note
definitely use __packagehome__ if you intend to share the question type with any questionnaire in any other account, or move the question type to another account. It will make sure that all files keep referencing each other correctly even though the paths change.
line 6: here we draw the div that will be used by your script to draw the question in. Note that the id contains a dynamic tag which inserts the questions variable name. This makes sure that id’s of elements are unique on your page, even when you display two items with this question type on one page
line 8: this section is the script that will initiate drawing and further handling of your question type
line 10: this line initiates your script and passes the current item definition to it
The JavaScript¶
- This script typically has a few functions:
- Drawing the initial screen including all elements. This logic includes:
drawing visual elements
drawing the pre-answered state (eg in case the user clicks ‘back’ in his questionnaire)
Responding to user interaction
Providing validation to user data
Making sure the response gets sent to the server
This script is preferably placed in a separate file that matches the name of your question type, eg myQuestionType.js
Example: js/myQuestiontype.js
We’ve built for this purpose a question type in 50 lines of JavaScript code, including comments and white lines:
1/*
2 Question type based on: single response
3 This question types randomly picks an answer from the available answers and the respondent should guess
4*/
5(function($) {
6 $.fn.extend({
7 myQuestionType: function(item) {
8
9 item.randomlyChosenAnswer = Math.floor((Math.random() * item.panswers.length) + 1);
- Remarks per line:
line 7: this is the actual function that will draw and handle the question
line 9: this line picks randomly one of the answers and will handle that as the ‘right’ answer for this excercise
10 function redraw() {
11
12 if (item.currentpanswerno == item.currentlyDrawnActiveAnswer) {
13 return; // nothing changed during window resize: no need to redraw
14 }
15 // this question should POST Q1_1: 3
16 // in case of 'extra input' it should also post Q1_1Text: textual user input
- Remarks per line:
line 10: we’ll have a redraw function. Redrawing this question happens when the window is resized.
line 12: When resizing doesn’t influence the layout you want, you can just return
17 var cnt, controlsHTML = 'Controls should be hidden from the actual respondent but are included for comprehensive POST-ing of the data:<br>',
18 htmlDisplay = '',
19 panswer;
20
21 htmlDisplay = item.text + '<br><div class="fixedText">Hello let\'s play a game, try to guess the answer by clicking on any of the squares! (the correct answer is ' + item.randomlyChosenAnswer + ')</div>';
22
23 for (cnt=0; panswer=item.panswers[cnt]; cnt++) {
24 controlsHTML += '<input type="radio" name="' + item.varlabel + '" id="' + item.varlabel + '_' + panswer.panswerno + ' " value="' + panswer.panswerno + '"' + (panswer.panswerno == item.currentpanswerno?' checked':'') + '>';
25 htmlDisplay += '<div class="myQuestionTypeBox myQuestionTypeBox' + item.varlabel + (item.currentpanswerno==panswer.panswerno?' myQuestionTypeSelectedBox':'') + '" id="box' + item.varlabel + '_' + panswer.panswerno + '">' + panswer.panswertxt + '</div>'
26 }
27 $('#' + item.varlabel + '-wrapper').html(htmlDisplay + controlsHTML).ready(function(){
28 $('.myQuestionTypeBox' + item.varlabel).click(function() {
29 var value = $(this).attr('id').split('_')[1];
30 item.currentpanswerno = value;
31 $dd.validate(); // validates all questions on this page, so also ones not served by the current script
32 redraw() // or $dd.redraw(), should it be necessary to also redraw any other item on this page
33 })
34 });
35
36 item.currentlyDrawnActiveAnswer = item.currentpanswerno;
37 }
This piece of the code builds the HTML that draws the question. It creates related controls (the actual readiobuttons) that will be posted to the server. The actual controls do not need to be visible, they can be styled to be invisible for the user.
- Remarks per line:
line 29 to 32: this is the action taken when the respondent clicks on one of the div-elements we prepared:
the value related to the currently clicked div is read by reading it from id of the control, which is of form
VARNAME_Xidthe value is placed back into the item object
the global validator function is called: if all questions on this page agree, the user can click ‘next’, otherwise not
the local redraw function is called: the question is redrawn in its entirity but other questions on the page are not redrawn
Note
Your script does not actually have to send answers to the server. If you just make sure you have form elements on the page that have the correct names, the POST will happen as if it was a normal page with HTML controls.
The names of the controls can be derived by convention. For intance for a question of type ‘free text’ called AGE, the control name should be AGE.
38 $.dd.redrawers.push(redraw); // in case of window-resizing this will make it redraw automatically along with any other items on this page
39 $.dd.validators.push(function() {
40 if (item.randomlyChosenAnswer == item.currentpanswerno) {
41 // we're ok, hurray!
42 return true;
43 }
44 $dd.pageMsg += 'Answer ' + item.currentpanswerno + ' is not correct';
45 return false;
46 }); // add it to the list of other validators that may live on the same page
47 redraw();
48 }
49 })
50})(jQuery);
- Remarks per line:
line 38: the redraw function gets pushed on the stack. This has the benefit that we can have multiple questions on one page, each with their own redraw function, that get called on each resize
line 39: one question by itself might not validate a screen. Again, more than one question may be present on the page, so it might not enable the ‘next’ button by itself. Instead a validator function is pushed on the stack. Each validation function can do two things:
return true (the answer is approved) or false (the answer is not approved)
add text to
$dd.pageMsg. This will enable visual feedback to the user such as ‘The input you provided is not valid’
And that concludes the entire question type!
The css file¶
This file contains the formatting options that go with your question type. The css file does not contain any technology that is specific to dotData. When you set your question type up good, any designer should be able to set and tweak the css to their liking without too much knowledge of the internal workings of your script. This separates layout and functionality.
Example: css/myQuestiontype.css
.fixedText {
border: solid 1px black;
margin: 12px;
padding: 12px;
background-color: lightgreen;
}
.myQuestionTypeBox {
border: solid 1px black;
display: block;
margin: 20px;
padding: 20px;
background-color: yellow;
cursor: pointer;
}
.myQuestionTypeSelectedBox {
background-color: lightblue;
}
Multiple questions merged together into one¶
If you want mix-and-match question types with combinations of radio buttons, checkboxes and input boxes, you can take the following steps:
Merge the file with code like:
RenderObj.MergeQuestions('Age', 'Gender', 'Income', 'MyQuestionTypeFile.txt')in file MyQuestionTypeFile.txt, code snippets that are meant to be handled for the first item in [1#1 1#1] tags instead of the usual [# #]:
$(document).myQuestionType([1#1 __item__ 1#1], [2#2 __item__ 2#2]);
Translatability¶
If you want to have multi-lingual questions you can create a textlist in the questionnaire for instance named MyMessages.
Now, you can use these translated texts in your file:
38<script>
39 var myQuestionTypeMessages = [# MyMessagesJSON #];
40 alert(myQuestionTypeMessages[0]);
41 alert(myQuestionTypeMessages[1]);
42 <script type="text/javascript">
43 $(document).ready(function() {
44 $(document).myQuestionType([# __item__ #]);
45 });
46</script>
Developing on local¶
To speed up the development cycle, it’s possible to have the js and css file on your local machine, provided it has a small webserver, like in a LAMP or WAMP setup. This way you can use your favorite js/css editor, just hit save and refresh your browser.
To point the __package__ folder to your local drive, append this to the URL to the questionnaire:
&__localdev=1
This will change all references from:
http(s)://dotdata/youraccount/yourpackageto
http://localhost/youraccount/yourpackage
Alternatively you can set another domain name, port number and folder, so the result becomes for instance:
http://myhostedfiles.com:81/somefolder/youraccount/yourpackage
This is done by providing the leading part of the path to the __localdev parameter. Note that the path can not contain any & or / signs, so it should be urlencoded (http://www.url-encode-decode.com/)
&__localdev=http%3A%2F%2Fmyhostedfiles.com%3A81%2Fsomefolder%2Fyouraccount%2F
Note
http in the URL will automatically be converted to https when the page runs in a secure setting
Naming of your HTML controls¶
Even though the real interaction with the respondent is based on images, dragging, circles or other elements, the underlying POST always happens using (hidden) underlying HTML elements. This section describes the naming of the element as expected by dotData:
Free text¶
base variable is [label].
Example given a question labeled MyStory:
MyStory= [whatever is typed by the respondent]
For derived question types (any question type that translate to a single column of text or numeric value in your data file, eg slider) the naming is the same.
Single response¶
base variable is [label]. The text of the ‘other’ box should be sent as [label]text.
Example given a question labeled Occupation with an other... type of text box:
Occupation(values 1,2,3,4 or 5)
OccupationText(value is whatever is typed by the respondent)
For derived question types (eg semantic differential) the naming is the same
Multiple response¶
base variable is [label]_X where X is the subject number. The text of the ‘other’ box should be sent as [label]_Xtext.
Example given a question labeled Hobbies with an other... type of text box:
Hobbies_1, value (empty) or 1. Note that empty values don’t get posted by a browser so technically they’re missing.
Hobbies_1Text(value is whatever is typed by the respondent, considering that Hobbies_1 is sent as 1)
Multiple subjects¶
For question with multiple subjects, one underscore is added, so [label_1]:
[label]_X
For multiple response questions (that already has subjects), this extra dimension is called ‘contexts’. It leads to yet another underscore:
[label]_X_Y
Value persistence¶
One common question is ‘what happens if the respondent gave answer number 3 (for instance ‘red’) but I want to change the questionnaire to add another option on the second position.
The answer is that the value is stored with the id of the original text, hence the value is persistent. That means that in any export before the change, the value will be exported as ‘3: red’ and after the change the value will simply be ‘4: red’. That means you are free to add extra answer categories or change the order of existing answer categories, as long as the system can match the answer categories character by character.
Testing¶
A question type is considered ‘tested’ when the following occured:
when you select an answer, click ‘next’ and then ‘back’ to the question, the answer you gave should be displayed again. This method proves that the answer was saved into the database and then fed back into the browser again.
when you can’t break it. Often a respondent will to things that you might not anticipate. It’s a good idea to take a step back from your question type and think ‘what could a respondent do to make this question not work’.
when the data is in the data file. Since the data file is in most cases the deliverable, only when the test data is there and has the values you expect, the question is considered functional and tested
you have tried this on all relevant devices and in all relevant scenario’s
