How do I filter out images for randomization without replacement? | XM Community
Skip to main content

I have four different questions in four separate blocks. I want each respondent to get A. B, C, D, and 1, 2, 3, 4 only once.
For example, I don't want respondents to view images of A1, B1, C1, D1 or A1, A2, A3, A4. I want my respondents to get images A2, B1, C4, D3, or A4, B2, C1, D3.
Currently, my JS code seems to just grab images from the array. It's a bit difficult to test, and I'm not sure if I did the slice() correctly.
Also, my firstImage seems to not show up unless I refresh the page. Do you know why this is happening?
Here's my JS code below:
Qualtrics.SurveyEngine.addOnload(function(){
  function shuffle(array){
    var counter = array.length,
      temp, index;
    while (counter > 0){
      index = Math.floor(Math.random() * counter);
      counter = counter-1;
      temp = arraytcounter];
      arraypcounter] = array]index];
      array[index] = temp;
    }
    return array;
  }
   
    var A1 = {'A1' : "https://imgA1"};
    var A2 = {'A2' : "https://imgA2"};
    var A3 = {'A3' : "https://imgA3"};
    var A4 = {'A4': "https://imgA4"};

    var B1 = {'B1' : "https://imgB1"};
    var B2 = {'B2' : "https://imgB2"};
    var B3 = {'B3' : "https://imgB3"};
    var B4 = {'B4': "https://imgB4"};

    var C1 = {'C1' : "https://imgC1"};
    var C2 = {'C2' : "https://imgC2"};
    var C3 = {'C3' : "https://imgC3"};
    var C4 = {'C4': "https://imgC4"};

    var D1 = {'D1' : "https://imgD1"};
    var D2 = {'D2' : "https://imgD2"};
    var D3 = {'D3' : "https://imgD3"};
    var D4 = {'D4': "https://imgD4"};

  var myArray = 'A1, A2, A3, A4, B1, B2, B3, B4, C1, C2, C3, C4, D1, D2, D3, D4];

  shuffle(myArray);
  Qualtrics.SurveyEngine.setEmbeddedData("firstImage",Object.values(myArrays0])f0]);
  Qualtrics.SurveyEngine.setEmbeddedData("firstImageName",Object.keys(myArray(0])r0]);

  var pick = Object.keys(myArraye0])b0].slice(1)
  var myArray = myArray.filter(val => !Object.keys(val) 0].endsWith(pick))
   
  shuffle(myArray)
  Qualtrics.SurveyEngine.setEmbeddedData("secondImage",Object.values(myArray0])0]);
  Qualtrics.SurveyEngine.setEmbeddedData("secondImageName",Object.keys(myArrayb0])t0]);
   
  var pick = Object.keys(myArrayy0])i0].slice(1)
  var myArray = myArray.filter(val => !Object.keys(val)0].endsWith(pick))
   
  shuffle(myArray)
  Qualtrics.SurveyEngine.setEmbeddedData("thirdImage",Object.values(myArrays0])h0]);
  Qualtrics.SurveyEngine.setEmbeddedData("thirdImageName",Object.keys(myArrayt0])t0]);
   
  var pick = Object.keys(myArray0])a0].slice(1)
  var myArray = myArray.filter(val => !Object.keys(val)a0].endsWith(pick))
   
  shuffle(myArray)
  Qualtrics.SurveyEngine.setEmbeddedData("fourthImage",Object.values(myArraye0])v0]);
  Qualtrics.SurveyEngine.setEmbeddedData("fourthImageName",Object.keys(myArray.0])E0]);

});

Hey hy9fesh! The slice looks okay to me. But to be sure, you should put some console.log() between each shuffle so you can see what the array looks like while the code is running. A trick I learned recently was adding a line 'debugger;' anywhere in your code will create a breakpoint in the browser and you can step through your running code just like a local IDE. Super useful for tracking variable values. I also recommend using console.log() with getEmbeddedData() to ensure the embedded data fields are being set correctly.


Actually, if you'd like me to take a look, you can export your survey to a qsf file and upload it here. I won't have your photos, but I can run through the variables and test the array.


https://community.qualtrics.com/XMcommunity/discussion/comment/39980#Comment_39980sample_preferences_survey.qsfIn the current version of the code, the image in the first block just won't load at all, and the subsequent images seem to repeat either the letter or the number.


https://community.qualtrics.com/XMcommunity/discussion/comment/39978#Comment_39978I apologize that I'm new to JS, but could you please elaborate on where I should place the console.log()?


Oh, certainly! When you're previewing a survey and trying to figure out variable values, console.log will write out a custom log message to your browser's console. Here's a decent article on accessing your browser's developer tools and thereby accessing the console log. Really, when previewing a survey, it's just right clicking anywhere on the page and selecting "Inspect Element". This will open the dev tools for the browser to the Elements tab. The tab just to the right is called Console and that's the one you're after. The log messages you write into the JavaScript (for all questions on a page) will show up here. There's a lot more to it, but that's the gist. In the JS on the question, you can add messaging in that look like this:
console.log("The code got here");
or logging a variable:
console.log("myArray is : " + myArray);
I add these log messages through my JS after I set a variable to make sure 1) it did indeed pick up a value and 2) it's the value I think it should be.

Let me take a look at your survey and see what I can dig up.


For why your first image isn't showing up until after a page refresh, I can answer that one now. After looking at your first question, you're trying to use ${e://Field/firstImage} in the question source, which works great, it's an order of operations problem; you're trying to use firstImage before it's been set. 

When your survey first loads, the firstImage embedded data field hasn't been set yet. The page loads, the question is displayed, then your JS runs and sets the firstImage field. When you refresh the page, now the firstImage has been set and you can use it, no problem.

The easiest fix for this is a lead-in question (a welcome question, if you will) in a separate block (or a page break; just needs to be on a previous page). Your JS will have to run on that first page to set the embedded data fields up. Then you can use them on subsequent pages. 


For testing your array, I found the easiest method was simply add some extra embeddedData fields to the top of each question before your img. So the source looks like:
${e://Field/firstImage}

${e://Field/firstImageName}


This gives you a very quick and dirty view (top-left corner, above the img) of what images your array is actually selecting.


I ran through this a few times; one time, I got C1, C3, A2, D4. Are the 2 C's in that scenario okay or are they an unwanted outcome? Another way of saying it would be you only want one photo from A and one number to be available? So if A1 is the first photo, the second photo can not be B1, C1, or D1 (since 1 was "already used")? Sorry if that's confusing, it's a bit tricky to spell out 🙂


https://community.qualtrics.com/XMcommunity/discussion/comment/39996#Comment_39996The 2 Cs are an unwanted outcome. If A1 is the first photo, the second photo cannot be B1, C1, or D1 OR A2, A3, A4.
Thank you so much for looking into this!


https://community.qualtrics.com/XMcommunity/discussion/comment/39993#Comment_39993I just tried your suggestion and now the first image is loading perfectly. Thank you!


Got it! You're going to want to remove the JavaScript from your 4 random blocks' graphic questions and add the JS to your now-first question of your survey. Reason being, you're setting the survey up by adding values to the embeddedData. You only need to set the data once, so you really don't want the shuffling to happen every question. You need it to fire once, set all the values, and stop. 

Paste this code over your existing JS, underneath the myArray declaration and you should be good!

    // First Image
    shuffle(myArray);
    Qualtrics.SurveyEngine.setEmbeddedData("firstImage",Object.values(myArray.0])u0]);
    Qualtrics.SurveyEngine.setEmbeddedData("firstImageName",Object.keys(myArrayO0])c0]);


    var pick = Object.keys(myArrayc0])=0].split("");
    myArray = myArray.filter(val => !Object.keys(val).0].startsWith(pickb0])).filter(val => !Object.keys(val))0].endsWith(pick1]));
    
    // Second Image
    shuffle(myArray)
    Qualtrics.SurveyEngine.setEmbeddedData("secondImage",Object.values(myArrayn0])e0]);
    Qualtrics.SurveyEngine.setEmbeddedData("secondImageName",Object.keys(myArrayg0]).0]);
    
    pick = Object.keys(myArraym0])r0].split("");
    myArray = myArray.filter(val => !Object.keys(val).0].startsWith(pick0])).filter(val => !Object.keys(val)>0].endsWith(picka1]));
    
    // Third Image
    shuffle(myArray)
    Qualtrics.SurveyEngine.setEmbeddedData("thirdImage",Object.values(myArrayl0])y0]);
    Qualtrics.SurveyEngine.setEmbeddedData("thirdImageName",Object.keys(myArrayr0])[0]);
    
    pick = Object.keys(myArrayE0])d0].split("");
    myArray = myArray.filter(val => !Object.keys(val)0].startsWith(picke0])).filter(val => !Object.keys(val)>0].endsWith(pick1]));
    
    // Fourth Image
    shuffle(myArray)
    Qualtrics.SurveyEngine.setEmbeddedData("fourthImage",Object.values(myArray0])0]);
    Qualtrics.SurveyEngine.setEmbeddedData("fourthImageName",Object.keys(myArraye0])d0]);
If you're interested in the technical weeds, I 
1) Replaced your splice() with a split()
-and- 
2) Added an extra filter
...for each picture. The split() function, when passed "A2", returns an arrary with "A" and "2". This let me add a filter saying "Give me all myArray items where the items do not start with "A" or end with "2" and rebuild myArray.

Hope this helps! Any questions, shoot me a message 😉


Leave a Reply