Dynamic Looping Action

Screenshot of Logic Actions section with the dropdown list of available logical actions. "Loop this question" action is highlighted.

Dynamic looping is a specific logical action that allows you to loop a specific question multiple times. This action is beneficial when you have a list of questions to ask and want to avoid creating a separate question for each item. It is incredibly useful when the list of questions changes dynamically. For example, if you have a checkbox question and want to ask a follow-up question for each selected option, dynamic looping can streamline the process. You can loop any question type. When the question is looped, it has access to a specific dynamic variable, loop_item, which contains information about the current looping item.

To make the looping action work, you need to provide a list source. The list source can be a previous question of Matrix or Checkbox type or a JavaScript script that returns an array.

๐Ÿ“˜

Note

Most likely, with the looping action, you would use the "Always trigger action". If the list source is empty, the Looping Action will behave as a Skip action.

Using with Checkbox

Let's assume we have a checkbox question: "What type of fast food do you like?" with five options: "Pizza," "Burgers," "Hotdogs," "Burritos," and "Tacos." For each selected option, we want to ask a follow-up voice response question: "Why do you like [food_item]? When do you usually eat it?"

This is a perfect example of when to use looping.

  1. Create a Checkbox Question

    • Add a checkbox question with your food options.
  2. Add a Voice Response Question

    • Add a new voice response question.
  3. Configure Logic Actions

    • Go to the right-side section and expand the Logic Actions section.
    • Click on "Add action" to add a new action.
    • For the condition, check the "Always trigger action" checkbox because we want to loop this question regardless of the situation.
  4. Select Looping Action

    • Select the "Loop this question" action.
    • You should see two options: "Question" and "JS script." For this example, choose the "Question" option to use the previous question as a source.
    • Click on the dropdown at the bottom and select your checkbox question.
  5. Save the Action

    • Hit the "Save" button to save the action.

Congratulations! We have set the looping action. Now, let's work on the question copy. We wanted to ask a follow-up question: "Why do you like [food_item]? When do you usually eat it?" To pass the selected option into our question, use the {{loop_item}} variable.

So, the final question copy will be: "Why do you like {{loop_item}}? When do you usually eat it?"

That's it! ๐Ÿ™Œ

Using with Matrix


The process of using the looping action with a Matrix question type as a source is similar to that with a Checkbox. The main difference lies in how the looping list is generated and how it is passed to the question. The looping list is generated based on the answered rows of the matrix. For example, if you have a matrix with three rows and three columns, and you answered the first and third rows, the looping list will contain two items.

In comparison with a checkbox, a matrix loop_item is an object. This means loop_item has multiple properties that you can use. Here are examples of properties for a single matrix loop_item:

// Matrix question with radio column types
{
  "default_radio": {
    "col_id": "column_96e817a4b9c1423990c3061ea886735b",
    "value": "Column 1",
    "type": "radio",
    "row_title": "Row 1",
    "col_title": "Column 1"
  }
}

// Common use case is to access row and column
// For example: "Why you selected for {{loop_item.default_radio.row_title}} that it is {{loop_item.default_radio.col_title}}?"
// Matrix question with checkbox column types
{
  "default_checkbox": [
    {
      "col_id": "column_3b4bc8024c11405dbc5ba61b620c39a8",
      "value": "Column 2",
      "type": "checkbox",
      "row_title": "Row 1",
      "col_title": "Column 2"
    },
    {
      "col_id": "column_1a206b05b2284abc9f259cf8d6724a74",
      "value": "Column 3",
      "type": "checkbox",
      "row_title": "Row 1",
      "col_title": "Column 3"
    }
  ]
}

// Checkbox based matrix would be hard to use within the page pecause loop_item.default_checkbox
// is an array in this scenraio. But you can still access answers using array indexes 
// {{loop_item.default_checkbox[0].col_title}}
//Matrix with radio, checkbox, custom group radio, custom group checkbox, text, dropdown, numeric column types

{
  "default_radio": {
    "col_id": "column_96e817a4b9c1423990c3061ea886735b",
    "value": "Column 1",
    "type": "radio",
    "row_title": "Row 1",
    "col_title": "Column 1"
  },
  "default_checkbox": [
    {
      "col_id": "column_1a206b05b2284abc9f259cf8d6724a74",
      "value": "Column 3",
      "type": "checkbox",
      "row_title": "Row 1",
      "col_title": "Column 3"
    },
    {
      "col_id": "column_104de0c5c233433c88e6d7da18c2b4ec",
      "value": "Column 4",
      "type": "checkbox",
      "row_title": "Row 1",
      "col_title": "Column 4"
    }
  ],
  "radio_group1": {
    "col_id": "column_9aad19c5a50b42fa934fd666a0fd3cbd",
    "value": "Column 5",
    "type": "radio",
    "row_title": "Row 1",
    "col_title": "Column 5"
  },
  "checkbox_group1": [
    {
      "col_id": "column_679853f850c34bb8a51a5807a9e291df",
      "value": "Column 7",
      "type": "checkbox",
      "row_title": "Row 1",
      "col_title": "Column 7"
    }
  ],
  "column_e8f58c19cec24016b04365834a51de6e": {
    "col_id": "column_e8f58c19cec24016b04365834a51de6e",
    "col_title": "Column 9",
    "value": "Hello world!",
    "type": "text",
    "valid": true,
    "row_title": "Row 1"
  },
  "column_2fa0909924274b7ea0c5767ee0edfde6": {
    "col_id": "column_2fa0909924274b7ea0c5767ee0edfde6",
    "col_title": "Column 10",
    "value": 123,
    "type": "numeric",
    "valid": true,
    "row_title": "Row 1"
  },
  "column_d9ffd7c7e1be485da3304bf2a59cf900": {
    "col_id": "column_d9ffd7c7e1be485da3304bf2a59cf900",
    "col_title": "Column 11",
    "value": {
      "id": "option_3e3a701012044554977cebd12bbd9bf0",
      "value": "option 2"
    },
    "type": "dropdown",
    "valid": true,
    "row_title": "Row 1"
  }
}

// Accessing variables examples:
// Radio: {{loop_item.default_radio.row_title}} - {{loop_item.default_radio.col_title}}
// Checkbox: {{loop_item.default_checkbox[0].row_title}} - {{loop_item.default_checkbox[0].col_title}}
// group1 Radio: {{loop_item.radio_group1.row_title}} - {{loop_item.radio_group1.col_title}}
// group1 Checkbox: {{loop_item.checkbox_group1[0].row_title}} - {{loop_item.checkbox_group1[0].col_title}}
// Text column: {{loop_item.column_e8f58c19cec24016b04365834a51de6e.value}}
// Numeric column: {{loop_item.column_2fa0909924274b7ea0c5767ee0edfde6.value}}
// Dropdown column: {{loop_item.column_d9ffd7c7e1be485da3304bf2a59cf900.value.value}}

Using JS Script

Another option for a looping source is a JS Script. This option allows you to write JavaScript code to cover more complex logic. The script must return an array, which can be an array of strings, numbers, or objects. The script is executed on the respondent's device in a secure environment, with an execution limit of 5 seconds; after that, the script will be terminated.

It is common to use the script to aggregate multiple matrix questions together, randomly generate questions, and more. To learn more about the capabilities of the code, please refer to the Script Editor documentation.

Steps to Use JS Script for Looping

  1. Access Logic Actions

    • Go to the right-side section and expand the Logic Actions section.
    • Click on "Add action" to add a new action.
  2. Select Looping Action

    • For the condition, check the "Always trigger action" checkbox if you want to loop the question regardless of the situation.
    • Select the "Loop this question" action.
  3. Choose JS Script Option

    • You should see two options: "Question" and "JS script." For this scenario, choose the "JS script" option.
  4. Write and Add Your Script

    • Write your JavaScript code in the provided editor. Ensure the script returns an array.

    • Example:

      // Example data array representing food items
      const foodItems = ['Pizza', 'Burgers', 'Hotdogs', 'Burritos', 'Tacos', 'Sushi', 'Salad'];
      
      // Function to get a random subset of items from the array
      function getRandomItems(array, numItems) {
          const shuffled = array.sort(() => 0.5 - Math.random());
          return shuffled.slice(0, numItems);
      }
      
      // Number of items to select randomly
      const numItemsToSelect = 3;
      
      // Return the randomly selected items as the looping source
      return getRandomItems(foodItems, numItemsToSelect);
      
  5. Save the Action

    • Hit the "Save" button to save the action.

By following these steps, you can effectively use a JS Script for looping, enabling more complex and dynamic question generation.

Other Script Examples

/**
 * Use Case: Randomly Shuffling a List of Questions
 * 
 * Description:
 * This script takes a predefined list of questions and randomly shuffles them.
 * It is useful when you want to present questions in a random order to avoid 
 * bias or to make the survey more engaging. The script returns the shuffled 
 * array of questions as the looping source.
 */

// Example data array representing a list of questions
const questions = [
    'What is your favorite color?',
    'What is your favorite food?',
    'Where do you like to go on vacation?',
    'What is your favorite hobby?',
    'What kind of music do you enjoy?'
];

// Function to shuffle an array
function shuffleArray(array) {
    for (let i = array.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [array[i], array[j]] = [array[j], array[i]];
    }
    return array;
}

// Shuffle the questions array
const shuffledQuestions = shuffleArray(questions);

// Return the shuffled questions as the looping source
return shuffledQuestions;
/**
 * Use Case: Looping for Checkbox Matrix Question
 * 
 * Description:
 * This use case involves a matrix question composed entirely of checkboxes. 
 * The goal is to follow up on every selected item in the matrix that has been selected.
 *
 * Matrix Question Example: Security Preferences
 * 
 * Question: Which security features do you find important for each application?
 * 
 * | Application                | Two-Factor Authentication | Data Encryption | User Activity Monitoring | Regular Security Audits |
 * |----------------------------|---------------------------|-----------------|--------------------------|-------------------------|
 * | Email Service              | [ ]                       | [ ]             | [ ]                      | [ ]                     |
 * | Cloud Storage              | [ ]                       | [ ]             | [ ]                      | [ ]                     |
 * | Social Media Platform      | [ ]                       | [ ]             | [ ]                      | [ ]                     |
 * | Financial Management Tool  | [ ]                       | [ ]             | [ ]                      | [ ]                     |
 * | Online Shopping Platform   | [ ]                       | [ ]             | [ ]                      | [ ]                     |
 */


// Extract the matrix answers from dynamic variables
const { security_matrix } = dynamicVariables.fields;

function extractAnsweredItems(matrix) {
    const items = [];
    Object.entries(matrix).forEach(([row_id, answer]) => {
        if (Array.isArray(answer?.default_checkbox) && answer.default_checkbox.length) {
            answer.default_checkbox.forEach(checkboxItem => {
                items.push({
                    row_title: checkboxItem.row_title,
                    col_title: checkboxItem.col_title
                });
            });
        }
    });
    return items;
}

return extractAnsweredItems(security_matrix) || [];
/**
 * Use Case: Combining and Shuffling Matrix Question Answers
 * 
 * Description:
 * This script accesses the answers from two radio button matrix questions, 
 * combines them, shuffles the combined list, and returns the first five items.
 * Each returned object includes the row and column titles, enabling question generation.
 */

// Extract the matrix answers from dynamic variables
const { security_matrix, customer_support_matrix } = dynamicVariables.fields;

// Function to shuffle an array using Fisher-Yates algorithm
function shuffleArray(array) {
    for (let i = array.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [array[i], array[j]] = [array[j], array[i]];
    }
    return array;
}

// Function to extract answered rows from a matrix
function extractAnsweredRows(matrix) {
    return Object.entries(matrix).map(([row_id, answer]) => {
        if (answer?.default_radio?.value) {
            return { 
                row_id,
                row_title: answer.default_radio.row_title,
                col_title: answer.default_radio.value
            };
        }
    }).filter(item => item !== undefined);
}

// Combine the answers from the two matrices and shuffle them
function combineAndShuffleMatrices(matrix1, matrix2) {
    const combined = [
        ...extractAnsweredRows(matrix1),
        ...extractAnsweredRows(matrix2)
    ];
    return shuffleArray(combined);
}

// Combine, shuffle, and return the first 5 items
const shuffledItems = combineAndShuffleMatrices(security_matrix, customer_support_matrix);
return shuffledItems.slice(0, 5).map(item => ({
    row_id: item.row_id,
    row_title: item.row_title,
    col_title: item.col_title,
    question: `For ${item.row_title}, why did you select ${item.col_title}?`
}));

Looping With Fields Variables

When you assign a variable name to a question that uses looping, the system automatically adds a suffix to the variable name for each loop iteration. For example, if your variable name is myQuestionAnswer, the first loop item will remain as myQuestionAnswer, while the following items will have an incremented suffix: myQuestionAnswer_1, myQuestionAnswer_2, and so on, depending on the number of items in the loop.

If you want to customize the suffix for each loop item, ensure that each item in the loop includes a field called loop_id. This allows for more meaningful identifiers in the generated variables. Please make sure that the loop_id values are unique to avoid assigning multiple values to the same variable name.

Hereโ€™s an example:

// Let's assume that our field variable name is "brand".
// Responding to this list will create fields like "brand_mcd", "brand_coke", "brand_apple", etc.

return [
  { label: "McDonald's", loop_id: "mcd" },
  { label: "Coca-Cola", loop_id: "coke" },
  { label: "Apple", loop_id: "apple" },
  { label: "Nike", loop_id: "nike" },
  { label: "Amazon", loop_id: "amazon" },
  { label: "Starbucks", loop_id: "starbucks" },
  { label: "Samsung", loop_id: "samsung" },
  { label: "Tesla", loop_id: "tesla" },
  { label: "Netflix", loop_id: "netflix" },
  { label: "Google", loop_id: "google" }
];

Looping in Results Dashboard

Partial Responses

In partial responses, dynamic variables for looping questions are not passed. However, the questions are marked to indicate which ones are looping. We recommend setting up partial response autosubmit if you need the variables to be populated into the question.

By Respondent

In the By Respondent tab, dynamic variables are populated into the questions for looping, and all the answers under the loop are processed as usual items. Data from looping questions is also included in response AI summaries.

By Question

In the By Question tab, each looping answer is considered an independent answer. This means theme and total sentiment analysis is aggregated, which might impact general accuracy. Currently, this is a platform limitation, but we would suggest using Results API for custom grouping of themes and sentiment.

Looping in Results API

Overview

In the Results API, each answer in the response includes two important fields related to looping: loop_index and loop_item. These fields help identify the context of each looping instance.

  • loop_index: This field identifies the iteration of the loop, with the default value being 0. It helps to distinguish between multiple iterations of the same looping question.
  • loop_item: This field contains the data passed from the loop, providing context such as the specific item being asked about in that iteration. (Default is null)

When probing is enabled, probing items also contain the loop_index field, which can be mapped to the main looping question to maintain the context of each probe.

Example JSON Structure

Here is a simplified example of a JSON response from the Results API, demonstrating how loop_index and loop_item are used:

{
    "data": {
        "id": "response_id",
        "answers": [
            ...
            {
                "id": "answer_id_2",
                "value": null,
                "audio_url": "audio_url.mp3",
                "transcript": "Two-Factor Authentication is needed for Email Service...",
              	// FIRST LOOP
                "loop_index": 0,
                "loop_item": {
                    "row_title": "Email Service",
                    "col_title": "Two-Factor Authentication"
                }
            },
            {
                "id": "answer_id_3",
                "value": null,
                "audio_url": "audio_url.mp3",
                "transcript": "For Cloud Storage, I think we just need it.",
              	// SECOND LOOP
                "loop_index": 1,
                "loop_item": {
                    "row_title": "Cloud Storage",
                    "col_title": "Data Encryption"
                }
            }
        ],
        "probes": [
            {
                "answer": {
                    "type": "audio",
                    "url": "audio_url.mp3"
                },
                "transcript": "Two-Factor Authentication is needed for Email Service...",
              	// PROBING FOR FIRST LOOP
                "loop_index": 0
            },
            {
                "answer": {
                    "type": "audio",
                    "url": "audio_url.mp3"
                },
                "transcript": "For Cloud Storage, I think we just need it.",
                // PROBING FOR SECOND LOOP
                "loop_index": 1
            }
        ]
    }
}