the:behavioral:lab

Archive for the month “May, 2016”

Custom Timers and Time’s Up Notifications in Qualtrics

I recently had to program a study in Qualtrics that put participants under time pressure. In addition to instructions, etc. I decided I wanted to put a timer at the top of the page that was static as people scrolled through the questions (as opposed to disappearing when people starting scrolling). Additionally, I wanted to run some JavaScript after time ended. To further add pressure, when time is up, I wanted the background to flash red and white. Qualtrics has a wonderful countdown clock feature in its timing question, but to do the custom stuff I wanted to do, I needed to create my own timer.

CSS

Normally, I would start with structure (HTML), but since the HTML is made via JavaScript, I will start with the formatting. One element will contain everything (I will give it the class header-cont, short for container). I make it take up 100% of the width of its container (in this case the whole page) and set it’s position to fixed. This is what keeps the element visible even when scrolling. Next, I put it 0 pixels from the top of the page, ensuring that nothing will appear above it. Last, I set the z-index as an arbitrarily high number so that elements of the Qualtrics survey don’t overlap it.

The CSS for the other elements is mostly visual (colors, font size, etc.). It’s somewhat easy to follow if you want to change things. The header class will be for another container element, and the timer class is for the element that will contain the timer text (this can be done with fewer elements, but with this structure the timer will be more portable to other projects). This CSS should go in the Custom CSS area within the Look & Feel menu under the Advanced Tab.

 .header-cont {  
   width:100%;  
   position:fixed;  
   top:0px;  
   z-index:1000;  
 }  
 .header {  
   height:75px;  
   background:#FFFFFF;  
   width:100%;  
   margin:0px auto;  
 }  
 .timer{  
   width: 500px;  
   margin: auto;  
   text-align: center;  
   vertical-align: middle;  
   line-height: 75px;       
   font-size: 24px;  
 }  

JavaScript considerations

The timer’s functionality, including the HTML elements that structure and visually present the time are all created in JavaScript. There are a number of considerations. One major one regards the theme you choose for your survey from the Look & Feel menu. As that menu name suggests, the themes don’t just affect the look of the survey. One element of the “feel” is how the survey transitions to different pages. In some themes (I’ll call static themes), a new page of your survey requires loading a new web page. These means loading a fresh HTML document, and anything we do to change the HTML document (like add a timer) will be gone. Other themes (dynamic themes) change survey pages by dynamically loading content without loading a new HTML document. This means that when you move to the next page, your timer will remain. This creates at least four possibilities

  1. You use a static theme and want the timer only on one page.
  2. You use a dynamic theme and want the same timer to countdown across all pages after the timer starts, without starting over.
  3. You use a dynamic theme and want the timer only on one page.
  4. You use a static theme and want the same timer to countdown across all pages after the timer starts, without starting over.

Options 1 and 2 are the easiest, and require the same solution. Option 3 is slightly more difficult, but what most people will encounter (e.g. I usually use the Minimalist theme, which is dynamic. The colors, font size,etc. below work well with this theme.). I cover that second, but I encourage you to read the solution for options 1 and 2 first, since I put more explanation there. Option 4 is the most complicated, because it is hard to tell the next page that is loading what time the last page ended on/what the timer on the current page should start on. I may get to it soon. If you are interested, email me (hint: the easiest solution would save the time to an embedded data field).

JavaScript – Options 1 and 2

To create the JavaScript, I first created all the questions on the page, and added a Timing question as well. It is not required that you put this JavaScript in a Timing question, but I wanted to know the total time people spent doing the task, and all the other questions had their own JavaScript cluttering up everything.

I separated the code into logical tasks, but it should all just be copy and pasted, in order, into the JavaScript editor in Qualtrics. This first part creates the HTML elements and adds them to HTML document.

 var headerCont = document.createElement("div");  
 headerCont.className = "header-cont";  
 headerCont.id = "header_container";  
 var header = document.createElement("div");  
 header.className = "header"  
 header.id = "header_1";  
 var timer = document.createElement("div");  
 timer.className = "timer";  
 timer.id = "timer_1";  
 timer.innerHTML = "Time Remaining: <span id='time'>00:30</span>";  
 headerCont.appendChild(header);  
 header.appendChild(timer);  
 document.body.insertBefore(headerCont, document.body.firstChild);  

The first nine lines of code successively create an element and give it a class name and ID to identify them later. While, in this case, class names aren’t necessary since there is only one instance of each type of class, I use them anyways, as it’s how I would program it for larger projects. Within the timer element, I add raw HTML (which is just text and single element). The span is how the JavaScript is going to know where to put the updated timing text. The final three lines, put the header inside the header container, then the timer inside the header, and finally adds the header container (which now contains the other elements) to the body of the HTML document so it can be seen by participants. This isn’t necessary, but the last line specifically adds the element as the first element in the body. appendChild() should work equally as well for this usage, but if you didn’t want a fixed positioning and wanted the header at the top, this would be necessary.

The final step is programming a timer that updates the span I created previously, and executes a function when the timer hits 0.

 function startTimer(duration, display) {  
  var timer = duration, minutes, seconds;  
  var myTimer = setInterval(function() {  
   minutes = parseInt(timer / 60, 10)  
   seconds = parseInt(timer % 60, 10);  
   minutes = minutes < 10 ? "0" + minutes : minutes;  
   seconds = seconds < 10 ? "0" + seconds : seconds;  
   var text = ('innerText' in display)? 'innerText' : 'textContent';
   display[text] = minutes + ":" + seconds;  
   if (--timer < 0) {  
    clearInterval(myTimer);  
    timeOver();  
   }  
  }, 1000);  
 }  
 var timerSeconds = 30,  
 display = document.querySelector('#time');  
 startTimer(timerSeconds, display);  
 var timeOver = function() {  
  document.getElementById("timer_1").innerHTML = "Time is up.";  
  x = 1;  
  var bgColor = setInterval(change, 1000);  
 }  
 function change() {  
  if (x === 1) {  
   color = "red";  
   x = 2;  
  } else {  
   color = "white";  
   x = 1;  
  }  
  document.body.style.background = color;  
 }  

The startTimer() function takes two parameters. The first is how many seconds you’d like the timer to last. The second is the ID of the HTML element you’d like the timer to appear in. Within the function, the myTimer variable gets assigned an interval (a function that executes at certain time intervals indefinitely until you tell it to stop). The first 6 lines of the interval function parse the current time into a readable format (e.g. representing 4 seconds as 00:04 instead of :4 and update the text content of the timer container). The last element tests if the timer is over. If it is, it clears the interval (not necessary, but Qualtrics will get mad at you if your JavaScript includes an interval but not a clearInterval() statement), and importantly executes a new function (timeOver()). Below that initial function, three lines set the number of seconds for the time, identifies the span that contains the timer text, and activates the timer. Finally, the timeOver() function changes the text of the timer, which now should read “Time remaining: 00:00,” to “Time is Up.” and creates a new interval. The interval function (named change) changes the background color of the body element of the HTML document to red if x is 1 and white if x is 2, and also changes the value of x. The end result is the background flashing red and white to signal that they need to hurry up and finish.

JavaScript – Option 3

If you are using a theme where you need to get rid of the timer manually, there are some complications. The main issue is that when the new content is loaded the old content, including the JavaScript used to create the timer, is removed. However, intervals are still active, so functions will keep running until you tell them to stop. Telling them to stop is difficult, because since the old JavaScript is removed, you can no longer reference the functions by name. One hack would be to stop all active intervals. However, since I don’t know what intervals Qualtrics may use in the functionality of the surveys, I did not want to risk mucking up other aspects of the survey. Instead, my fix was to manually add the JavaScript for the timer inside the HTML elements I created. It sounds easy enough, but it requires all the code on a single line (which is very messy if not difficult).

I will start with the code to clear everything, since it will help when explaining changes made to the earlier code. This code can go in any question on the page immediately following the page the timer is on. If there are multiple possible pages, the code can go on all of them. If, like my project, the landing page sometimes had a timer before it, and sometimes didn’t, this will still work without error.

 if (document.contains(document.getElementById("header_container"))) {  
  if (myTimer) clearInterval(myTimer);  
  if (bgColor) clearInterval(bgColor);  
  var el = document.getElementById("header_container");  
  el.parentNode.removeChild(el);  
  document.body.removeAttribute("style")  
 }  

The flow of the code is:

  1. Check if the timer exists. If you no timer, no need to run the code.
  2. If it does exist, check if the myTimer interval is running. If so, clear it.
  3. Do the same for the bgColor interval.
  4. Find and remove the header container element (and thus everything else).
  5. Remove the style attribute from the body element (if the page was blinking red, it will revert to its default color).

Below is the JavaScript for the timer. It is similar to what’s above, but with some changes and I do not split it up by task. The main difference is where the JavaScript code for the timer goes. JavaScript runs as part of an HTML document. If JavaScript is running on a webpage, there is a script element somewhere that either contains the code or loads it from a file on a server. When you add JavaScript to a survey question in Qualtrics, it puts the script element in the header of the HTML document. It removes that element when a new survey page is loaded. I want to put some of the HTML somewhere else, so Qualtrics can’t remove it. Only I can. I do this by using JavaScript to create a script element within our other timer HTML elements. I then add text to the element. The text will be read and run as JavaScript code once the element is part of the HTML document.

 var headerCont = document.createElement("div");  
 headerCont.className = "header-cont";  
 headerCont.id = "header_container";  
 var header = document.createElement("div");  
 header.className = "header"  
 header.id = "header_1";  
 var timer = document.createElement("div");  
 timer.className = "timer";  
 timer.id = "timer_1";  
 timer.innerHTML = "Time Remaining: <span id='time'>00:30</span>";  
 var s = document.createElement('script');  
 s.type = 'text/javascript';  
 var code = 'var myTimer=false,bgColor=false;function startTimer(e,r){var t,n,i=e;myTimer=setInterval(function(){t=parseInt(i/60,10),n=parseInt(i%60,10),t=10>t?"0"+t:t,n=10>n?"0"+n:n;var e="innerText"in r?"innerText":"textContent";r[e]=t+":"+n,--i<0&&(clearInterval(myTimer),myTimer=!1,timeOver())},1e3)}function change(){1===x?(color="red",x=2):(color="white",x=1),document.body.style.background=color}var myTimer=!1,bgColor=!1,timerSeconds=30,display=document.querySelector("#time");startTimer(timerSeconds,display);var timeOver=function(){document.getElementById("timer_1").innerHTML="Time is up.",x=1,bgColor=setInterval(change,1e3)};';  
 headerCont.appendChild(header);  
 header.appendChild(timer);  
 try {  
  s.appendChild(document.createTextNode(code));  
  timer.appendChild(s);  
 } catch (e) {  
  s.text = code;  
  timer.appendChild(s);  
 }  
 document.body.insertBefore(headerCont, document.body.firstChild);  

The differences start on the line that begins with “var s = …” This line creates a script element. The next tells browsers that the element will contain JavaScript. The next contains all the modified JavaScript code for the timer. Since all the code has to be a single string, it has to be on a single line. To make this a little easier, I used a simple minify service. I’ll come back to minifying later, as well as the modified timer code in a second. The last change is the try…catch code blocks. The first block of code, which adds the JavaScript text to the script element using createTextNode, should work on most browsers, but just in case there is an error the second block of code should work.

Back to the minified code. Minifying code removes all unnecessary white space, changes some variable names to single characters, and performs some light re-writing of your code to make code smaller. This is important for sites like Google, where unminified code may be several megabytes and take a minute or more to download (and therefore a minute for a page to load), but minified code is only a few kilobytes and loads in under a second. I used it mostly to get rid of white space (putting all code on a single line), and making it slightly less messy (e.g. the line of code would extend about 50 ft off to the right of you computer screen if I put the longer, human-readable code). One word of caution about minifying, if you are going to alter the code and then reminify: while I’m sure their methods are correct, the minifier removed the first line of code (initializing the variables that will store the intervals to false). Without this line, it will be harder to check to see if the intervals are active later when clearing everything. Add that line back if needed.

  var myTimer = false, bgColor = false;  
  function startTimer(duration, display) {  
   var timer = duration,  
    minutes, seconds;  
   myTimer = setInterval(function() {  
    minutes = parseInt(timer / 60, 10)  
    seconds = parseInt(timer % 60, 10);  
    minutes = minutes < 10 ? "0" + minutes : minutes;  
    seconds = seconds < 10 ? "0" + seconds : seconds;  
    var text = ('innerText' in display) ? 'innerText' : 'textContent';  
    display[text] = minutes + ":" + seconds;  
    if (--timer < 0) {  
     clearInterval(myTimer);  
     myTimer = false;  
     timeOver();  
    }  
   }, 1000);  
  }  
  var timerSeconds = 30,  
   display = document.querySelector('#time');  
  startTimer(timerSeconds, display);  
  var timeOver = function() {  
   document.getElementById("timer_1").innerHTML = "Time is up.";  
   x = 1;  
   bgColor = setInterval(change, 1000);  
  }  
  function change() {  
   if (x === 1) {  
    color = "red";  
    x = 2;  
   } else {  
    color = "white";  
    x = 1;  
   }  
   document.body.style.background = color;  
  }  

The first line initializes two variables to false. These variables will become the interval function later. However, if the either interval is never set (which is likely for the bgColor function for some participants), then an error will be thrown on the next page when clearing the timer. Next, when the timer ends, I reset the myTimer variable to false, since the interval is already cleared. That is about it for changes (mainly because I went back and added the other changes to the Option 1 and 2 code to make it look similar). If you need to edit this code, copy and paste into your editor, make edits, re-minify, add the first line back, and paste it within the quotation marks on the proper line above.

Post Navigation