Set Qualtrics Embedded Data with Javascript for loop - javascript

I am working on a survey in qualtrics, and need to be able to set many embedded data fields with different values, depending on a random selection of the experimental condition.
In the Survey Flow, I've entered all the names of the desired embedded data values.
I want this code to execute at the start of the survey, and to go through and set the values of those Embedded data variables.
The reason I use a for loop is because the variables are named things like:
set1a
set1b
set1c
var condition = Math.floor((Math.random() * 3) + 1)
var conditions =[ [8,8,8,5,5,5,5,2,2,2], [8,8,8,7,7,7,7,2,2,2], [10,10,10,5,5,5,5,2,2,2], [10,10,10,7,7,7,7,2,2,2]]
var values = condition[conditions]
var letters ="abcdefghij"
Qualtrics.SurveyEngine.addOnload(function(values)
{
for (var i = 0; i < values.length; i++ ) {
var dataname = "set1" + letters.charAt(i)
Qualtrics.SurveyEngine.setEmbeddedData(dataname,values[i]);
}
});
However, this code doesn't work.
When I try to call the variables using piped text later on in the survey (after a page break) they do not appear to have stored.

You have two issues.
First, you can only set Javascript in Qualtrics if it's inside of the .addOnload function, e.g.:
Qualtrics.SurveyEngine.addOnload(function(values)
{
// Your code
});
Second, your method for assigning - values - throws an error message.
Qualtrics doesn't have an issue saving embedded data in a for loop, so if you fix these two points, you should be good to go.

Related

Native JS alternative for Java's next() and hasNext() methods

So recently I built a search and replace program with Java, now I am working on translating/rebuilding that program with JavaScript. However, I am having trouble finding JS method alternatives for next() and hasNext(). I am new to JS so I don't know what JS methods would work similarly to the Java methods I am used to.
This is my program, I commented through it to show exactly what I am doing with the previously mentioned methods. Basic set up, 2 text areas, one for the search box (search criteria, box 2), and one for the main document (the field of search, box 1). It basically boils down to a cross-reference. It will highlight all the similarities between the documents.
function search() {
//define an array to store the search criteria.
var array = [];
// define a counter.
var n = 0;
// define a constant for the first box, the search field.
const box1 = document.getElementById("box1");
// define a constant for the second box, the search criteria.
const box2 = document.getElementById("box2");
// loop through the search criteria, storing each word as a seperate element in the array.
// this uses non js terms, this is where I need the help.
while (box2.hasNext()) {
array[n] = box2.next();
n = n + 1;
}
// resets the counter.
n = 0;
// loops through each search item, finding and replacing each item with itself, surrounded by mark tags.
while (n <= array.length) {
box1.replace(array[n], "<mark>" + array[n] + "</mark>");
}
}
</script>
There is bound to be other issues, bugs and syntax, feel free to point them out but lets try and keep the focus on the methodology (i.e. method alternatives for next() and hasNext()).
Thanks.
-EDIT- I'd prefer to use native alternative (no jquery) becuase I know even less about jquery than I do js.

Individual custom start position in Qualtrics through Javascript

I want to use either a slider question or a draggable bar chart in Qualtrics to present to respondents how they answered in former questions. More specifically, I compute a value out of then answers with weighting, and want the slider or bar to be positioned at this value.
Notably, as each respondent has a value (stored in an embedded data field), the position will thereby be individual for each respondent. Piping only works for text fields, as far as I understood the support page.
Based on this question/answer I came to the following code for the bar graph:
Qualtrics.SurveyEngine.addOnload(function()
{
var result = "${q://Field/result}";
var qwidth = $('QID1936~1~track').offsetWidth;
var resrec = ((qwidth*result)/100);
$('QID1936').select('.bar').each(function(name, index) {
name.setStyle({ width: resrec +"px"});
});
});
Basically, I get the result for each respondent out of the embedded data, get the width of the full bar graph, compute the ratio that should be colored based on the result, and update the position of the bar graph (following the mentioned answer).
Funny enough, everything works when done in the console. Also, the embedded data is correctly loaded, qwidth as well.
Two problems arise: it seems resrec could be computed wrongly, as a console.log() spits out 0 instead of the correct value. I assumed this could be somehow as a variable is not recognized as number, but several tries with Number() or 0+var did not change how this works in Qualtrics. In the console, it works just fine.
Also, no update of the bar (or slider with similar code) happens, neither with the correct value nor with the 0 that is produced by Qualtrics.
I search for two things: either a solution to the Javascript problem as described, basically how I can update the bar or slider with embedded data. Or another solution how to directly get embedded data into one of those two question formats as a starting value for each respondent individually.
Thanks for your help or ideas!
Try this:
Qualtrics.SurveyEngine.addOnload(function()
{
var qid = this.questionId;
var result = parseFloat("${e://Field/result}");
var qwidth = $(qid+'~1~track').offsetWidth;
var resrec = ((qwidth*result)/100);
$(qid).select('.bar').each(function(name, index) {
name.style.width = resrec + "px";
});
});
Notes:
It is best not to use a hardcoded QID
In a pipe use e: to refer to an embedded variable. q: is for questions.
Use parseFloat to convert the string to a number
No need to use setStyle if you are only setting one value
One solution proposed by Qualtrics support: when you use bars and/or sliders, piped values are actually possible.
The trick is to have the value of the bar/slider shown (a thing we do not use in the whole survey elsewhere). Then, you can access over the Advanced Question Options > Add Default Choices the blue arrow for piping text behind the value. Through this, the value is individually set to either embedded data or another answer.
Note, however, to tick "Show value" before accessing the default choices, else you will only be able to drag around the bar and set it for all simultaneously.
Here is a solution using the Qualtrics Question API method setChoiceValue that does not require you to compute the ratio and update the length of the bars manually.
Below is an example code for the result of ten respondents saved in embedded data from previous questions.
Qualtrics.SurveyEngine.addOnload(function()
{
var embedded = ["${e://Field/r1}", "${e://Field/r2}",
"${e://Field/r3}", "${e://Field/r4}", "${e://Field/r5}",
"${e://Field/r6}", "${e://Field/r7}", "${e://Field/r8}",
"${e://Field/r9}", "${e://Field/r10}"];
for (var i = 0; i < 11; i++) {
var index = i + 1;
var choiceInput = embedded[i];
this.setChoiceValue(index, choiceInput);
}
});
For one respondent:
Qualtrics.SurveyEngine.addOnload(function()
{
var value = "${e://Field/r1}";
this.setChoiceValue(1, value);
});

Copying & Modifying specific values from each Form submit when copying to another sheet

I got a situation where 2 independent parties need access to the same data, but their back-end that uses the data needs a different format, e.g. one party needs to see "Soccer", the other needs to see "1".
This input comes from various users through a Google Form.
I previously ran a time interval script that would copy over all data from sheet 1, then copy it to sheet 2, then a double loop would kick (using var i) in to search all columns and replace specific strings on sheet 2 with a numeric value.
This script worked, but due to it's inefficiency it started to crash once I started to have more data.
So I am trying to reduce the dataset by just handling the data that comes from each individual Form Submit.
I tried 2 different approaches:
1) Grab values to copy, modify values, then copy to new sheet
2) Grab values to copy, copy to new sheet, then modify these new values (lastrow)
I know the copy bit works in both scripts I wrote but my modification does not, when using the debugger it shows me the modified data is same as the original and this is in fact what happens the copy is identical.
I realize I am probably making a very basic mistake, but I am blind as to what it is. Searching for similar threads on Stackoverflow and other sites I did not come to a resolution.
Current code:
function onFormSubmit(e){
var responses = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var projects = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet2");
var row = responses.getLastRow();
var col = responses.getLastColumn();
var copy = responses.getRange(row, 1, 1, col)
var modify = copy.getValues();
for (var i=0; i < modify.length; i++){
if (modify [i] == "SearchString1"){
modify [i] = "1";
}
else if (modify [i] == "SearchString2"){
modify [i] = "0";
}
else if (modify [i] == "SearchString3"){
modify [i] = "1";
}
projects.appendRow(modify);
}
}
This code will copy the data, but not modify it.
I had a variation where i used .setvalues but that resulted in the same end result.
Solved, it was all about location identifiers.
The getvalues created a double array and the append was in the loop instead of outside of it. I had to specify the append and modification better.
Fixed code:
for (var i=0; i < modify[0].length; i++){
if (modify[0][i] == "SearchString1"){
modify[0][i] = "1";
}
else if (modify[0][i] == "SearchString2"){
modify[0][i] = "0";
}
else if (modify[0][i] == "SearchString3"){
modify[0][i] = "1";
}
}
projects.appendRow(modify[0]);
}

Custom function for Google Spreadsheet - handling the array passed from the spreadsheet

I am trying to build a simple script to work with a Google Spreadsheet. The spreadsheet takes input from a Google Form, so there is a series of values in the spreadsheet like this:
My goal is to write a script that would strip the number from each form input in a user-specified range, then add all the numbers to provide a single score. So, for example, the user could type =sumColumns(H2:K2) in a cell, and it would return the sum of the scores (for the sample screenshot I posted, it would return the result of 3+3+0+3, 9).
Here is the code that I wrote to do this:
function sumColumns(values) {
var sum = 0;
for(var i = 0; i <= values.length; i++){
var input = values[0][i];
var x = input.toString();
var y = x.charAt(0);
var num = parseInt(y);
sum += num;
}
return sum;
}
The problem is that it only ever seems to add two values together. So, when I put =sumColumns(H2:K2) in a cell in the spreadsheet, it only returns 6. Also, on line 3, if I change it from i <= values.length to i < values.length it only adds one number, so that I get 3 as a result. My guess is that I am misunderstanding the way that the Google Spreadsheet values are passed to the function, but I have been completely unable to make it work. I'd really appreciate any help!
Oops - edited & saved the question, wrote an answer - and forgot to save it. I let Serge beat me to it! And, as usual, Serge's answer works well (with integer values). But you did ask about how things worked, so here you go.
When you give a Custom Function a range as a parameter, H2:K2 in this case, the function receives a two-dimensional array, equivalent to the return value of Range.getValues(). You can test this easily, by (temporarily) changing your function to return a JSON representation of the parameter:
function sumColumns(values) {
return JSON.stringify(values); // For debugging, just return string showing values
...
Here's what you'll see in the cell that contains =sumColumns(H2:K2):
[["3 (Rarely)","3 (Frequently)","0 (Never)","3 (Frequently)"]]
That's showing an Array enclosed by [ .. ], with another Array inside, also enclosed by square brackets, and that array has four elements. If we change the range to be H2:K3 instead, we get this (with whitespace added for clarity):
[
["3 (Rarely)","3 (Frequently)","0 (Never)","3 (Frequently)"],
["","","",""]
]
Now that you know that, it's easy to see why your function was giving the results it did.
First, for(var i = 0; i <= values.length; i++) is using the wrong array bounds to loop over, since values.length will tell us how many rows are in values. In H2:K2, that length is 1. Instead, we need to be looping over the columns in the first row (values[0]), with its 4 cells.
You were wondering about < vs <= for this loop - we do need to use < since it's a 0-based index, and .length returns a count of elements. So we end up with:
for (var i=0; i < values[0].length; i++){ ... }
Using parseInt() is a good choice, and works well for the values in your spreadsheet. It can be improved, though, by ensuring that any String values have leading non-numeric values stripped first - parseInt() can then find an Integer inside a string.
function sumColumns(values) {
return JSON.stringify(values); // For debugging, just return string showing values
var sum = 0;
for(var i = 0; i < values[0].length; i++){
var input = new String(values[0][i])
.replace( /^\D+/g, ''); // Strip any leading non-digits
sum += parseInt(input);
}
return sum;
}
I'm not good with custom function because I never use them but it seems that values is not really an array...
Comments in italic :
Hmmm embarrassing ... my first though was that it had to be a 2D array but I logged val[0] in my test and it returned an 'undefined' error... I must have mistyped something at that moment... Anyway, that's why I looked for a way around handling data as a string and using split and regex.
As usual with Mogsdad's answers you have an answer and all the explanations that go with it ;-) and, as often with him too, you get a better answer than mine.
(one restriction though (#Mogsdad) your comment about non integer values could be applied to your code as well... you simply strip out any decimal value with parseInt()...:-)
That said, your use case was well described and in the limits of this example both code should work as expected, Mogsdad's one being more 'academic' and programmatically correct.
end of comment.
Using this trick below it works as expected for any input range (1 or more row and columns):
function sumCol(val) { // returns the sum of all numeric values in range
var values = val.toString().split(',');
var sum = 0;
for(var n=0;n<values.length;++n){
sum+=Number(values[n].replace(/[^0-9+.]/ig,''));
}
return sum;
}
I changed also the number extraction mode to make it more universal.

Dictating Save Order of Controls in VB.Net

I have a parent page that contains a placeholder.
Span elements that contain IFrames are added to this placeholder as new IFrames are needed.
Each IFrame contains an asp control.
The parent has this code, fired onClick of the parent page:
function saveAll() {
for (i = 0; i < frames.length; i++) {
for (j = 0; j < frames[i].length; j++) {
if(frames[i][j] != null && frames[i][j] != ''){document.getElementById(frames[i][j].toString()).contentWindow.Save();}
}
}
}
which calls a save function within each control, which in turn, fires a button click, which fires the server side save function for that specific control. To someone trying to read this in 30 seconds...the previous sentence might be found to be confusing...so hopefully this might clarify the big picture here:
The problem I'm having is that the server is processing the controls out of order, despite the frames being ordered.
the frames object in the aforementioned javascript is a 2D array organized like this:
frames
[[controlA_instanceA,controlA_instanceB,controlA_instanceC],[controlB_instanceA,controlB_instanceB],
[controlC_instanceA,controlC_instanceB,etc],[etc]]
The loop, the firing of the child controls, and the frames array is all working correctly (hence why I didn't show the code here). However, could someone point me in the right direction on how to enforce the order the server processes the controls in?
Your controls need a sequence number assigned to them. Then the first step of your save all would be to decompose the 2D array into a 1D array organized by the sequence number.
When you are adding an item to the frames array you need to add them as an object with two members:
var seqNo = 0; // at some point early in the script
...
frames[x][y] = {frame: (iFrame), sequence: seqNo}; //When the Control is added
seqNo++;
where (iFrame) is the id of the frame.
Then your save all function would look like this:
function saveAll() {
var iFrames = [];
for (i = 0; i < frames.length; i++)
{
for (j = 0; j < frames[i].length; j++) {
if(frames[i][j] != null && frames[i][j] != '')
{
iFrames.push(frames[i][j]);
}
}
}
iFrames.sort(function(a,b){ return a.sequence - b.sequence});
for ( t = 0; t < iFrames.length; t++)
{
document.getElementById(iFrames[t].frame.toString()).contentWindow.Save();
}
}
In this way you are ensuring that the order is preserved because you sorted the array by the sequence number.
This is to say that the problem could be that the array appears to be sorted but it is not and and not assuming the issue is the order in which the server is responding to the click events.
EDIT:
If the server is responding to the events out of order then you may need a different approach.
If this is an ajax site and you are posting back via a web method then I would suggest creating a new web method that takes an array as a parameter.
Push all the content you wish to save into the array and then pass it back to the server for processing in a specific order.
If this is a classic forms site then you can handle a custom postback in your page load event.
Either way it sounds like you need to re-evaluate the way this is processing.

Categories

Resources