Zu Hauptinhalten wechseln

FV Decipher Unterstützung

Alle Themen, Ressourcen für FV Decipher benötigt.

 
decipher

Loop Tag: Cycle Through Questions

Using the survey builder? Check out the Loop Element.

Overview

The <loop> element is used to cycle through a series of survey elements. With each cycle, a new set of values can be supplied to show the same survey elements with new content.

For example, here's a "T-Minus countdown timer" that starts from 10.

<loop label="T_Minus" vars="time">
  <block label="Launchpad">

    <html label="Launch_Status_[loopvar: label]" where="survey">
      ROCKET LAUNCH: T-MINUS [loopvar: time] SECONDS
    </html>

  </block>
  <looprow label="10"><loopvar name="time">10</loopvar></looprow>
  <looprow label="9"><loopvar name="time">9</loopvar></looprow>
  <looprow label="8"><loopvar name="time">8</loopvar></looprow>
  <looprow label="7"><loopvar name="time">7</loopvar></looprow>
  <looprow label="6"><loopvar name="time">6</loopvar></looprow>
  <looprow label="5"><loopvar name="time">5</loopvar></looprow>
  <looprow label="4"><loopvar name="time">4</loopvar></looprow>
  <looprow label="3"><loopvar name="time">3</loopvar></looprow>
  <looprow label="2"><loopvar name="time">2</loopvar></looprow>
  <looprow label="1"><loopvar name="time">1</loopvar></looprow>
</loop>

The code above produces the following result (pause to view the animation):

T-Minus 10

Loops are the ideal choice for repetitious tasks such as brand or concept tests, special question types such as DCMs or MaxDiffs, and iterating over questions that vary slightly in detail.

Loops help improve productivity.
DRY (don't repeat yourself): they'll save you from writing the same thing more than once
Brevity: they'll reduce your lines of code
Maintainability: they'll save you time when changes are needed

1:  How to Write a Loop

The <loop> element's basic syntax is below:

<loop label="LOOP_LABEL" title="LOOP TITLE" vars="VAR1,VAR2">
  <block label="BLOCK_LABEL">

    <note>CONTENT TO REPEAT GOES HERE</note>

  </block>
  <looprow label="1">
    <loopvar name="VAR1">VARIABLE 1.1</loopvar>
    <loopvar name="VAR2">VARIABLE 1.2</loopvar>
  </looprow>
  <looprow label="2">
    <loopvar name="VAR1">VARIABLE 2.1</loopvar>
    <loopvar name="VAR2">VARIABLE 2.2</loopvar>
  </looprow>
</loop>

Starting from the top, here's a brief description of each component:

<loop label="LOOP_LABEL" title="LOOP TITLE" vars="VAR1,VAR2">
    ...
</loop>

This is the main <loop> element definition. Like all survey elements, the loop's label must be unique. The title attribute appears only in the survey builder as a description of the loop. The vars attribute should contain a comma-separated list of each variable that will be piped into the loop's contents.

    ...
  <block label="BLOCK_LABEL">

    <note>CONTENT TO REPEAT GOES HERE</note>

  </block>
    ...

The <loop> element requires one <block> element to contain all of the content to loop through. This is where all questions, comments, and exec blocks should be specified.

An implicit page break (<suspend/>) is added to the end of every loop. This can be removed by specifying suspend="0" in the <loop> element.

    ...
  <looprow label="1">
    <loopvar name="VAR1">VARIABLE 1.1</loopvar>
    <loopvar name="VAR2">VARIABLE 1.2</loopvar>
  </looprow>
  <looprow label="2">
    <loopvar name="VAR1">VARIABLE 2.1</loopvar>
    <loopvar name="VAR2">VARIABLE 2.2</loopvar>
  </looprow>
    ...

The <loop> element's content will be shown once for every <looprow> element specified. There are two loop cycles in the code above.

Variables are defined using the <loopvar> element. These are the changing parts of each loop. Each variable specified in the <loop> element's vars attribute must appear once as a <loopvar> for every <looprow>.

A <loop> can contain any number of <looprow> elements and a <looprow> can contain any number of <loopvar> elements.

These variables can be accessed within the loop using the [loopvar: VARIABLE_NAME] syntax. For example:

  • [loopvar: label] : returns the current <looprow> label
    e.g. 1, 2
  • [loopvar: VAR1] : returns the current loop's <loopvar> named VAR1
    e.g. VARIABLE 1.1, VARIABLE 2.1
  • [loopvar: VAR2] : returns the current loop's <loopvar> named VAR2
    e.g. VARIABLE 1.2, VARIABLE 2.2

 

2:  Loop Options & Attributes

Below is a list of the attributes available to <loop>, <looprow> and <loopvar> elements:

Attribute Description

<block>                 The content of the loop. Will be shown                                                once for every looprow specified.

label The unique identifier for the loop element.
title The loop's title is optional and only visible in the survey builder. Use this to provide more detail about the loop.
vars A comma-separated list of variables to use for each loop.
translateVars A comma-separated list of variables to translate via the translation system. By default, all variables are translated. Use this attribute to explicitly define the attributes that should be translateable.
randomizeChildren A boolean value that controls the shuffling of the <looprow> elements. Specify randomizeChildren="1" to shuffle the loop cycles.
count

An integer value for the total number of loop cycles to show. When enabled in a nested loop, loop items shown in the first iteration will be shown in subsequent iterations.

Note: This attribute only works when randomizeChildren="1" is set.

suspend A boolean value to control the implicit page break that is automatically added between every loop cycle (after every <looprow>). Specify suspend="0" to remove this implicit page break.
cond The condition in which the entire <loop> element should be shown. If the condition evaluates to False, the <loop> will not be evaluated.
<looprow>              Each iteration within a loop.
label The unique identifier for the <looprow> element. This is often used to create unique variables for everything added to the <loop> element's content area.
e.g. <radio label="Q1_[loopvar: label]">
cond The condition in which the <looprow> should be executed. If the condition evaluates to False, the entire <looprow> will be skipped.
randomize

A boolean value to control the default loop rotation. Specify randomize="0" to anchor the loop iteration so that it does not rotate. If not randomized, the loop iteration will be shown in the order in which it is programmed.

Note: This is an XML-only attribute, and it requires that randomizeChildren="1" be added to the loop

looprows

Can be used to specify when an inner looprow should appear in an outer loop, and accepts a comma-separated list of labels from an outer loop. 

Note: This is an XML-only attribute.

<loopvar>                  Each variable within a loop iteration.
name The name of the variable. This should coincide with the variables specified in the <loop> element's vars tag.
e.g. <loopvar name="myVar">myVar's content>/loopvar>
target

Can be used on a loopvar to pipe in different values for different loop iterations (looprow). The target attribute accepts a looprow label in an outer loop and must be specified for all loopvar elements with the same name.

e.g. target="r1"

Note: This is an XML-only attribute. If in use, the loop will not be editable within the Survey Editor.

Questions & Cells
looprows The looprows attribute can contain a comma-separated list of <looprow> labels and acts like a condition, only showing the element it's applied to given that the <looprow> currently being shown exists in the list.
e.g. <row label="r1" looprows="1,2,a,b">Item 1</row>

3:  Accessing Loop Details in Python

When accessing loop details in Python using loop.index, loop.length and loop.enabledRows, you need to set a count="" attribute (i.e. count="99") in the <loop> element in order for those variables to operate as expected.

It's often important to know which of the <looprow> elements is currently being shown and how many will actually be shown to the respondent.

For example, given the following simple loop:

<loop label="MyLoop" vars="item" randomizeChildren="1">
  <block label="MyLoopBlock">

    <html label="LoopVariables_[loopvar: label]" where="survey">
      <p>Now viewing cycle # ${MyLoop.index} out of ${MyLoop.length}.</p>
      <p>The "item" variable is currently: [loopvar: item]</p>
      <p>This is for looprow with label: [loopvar: label]</p>
      <p>This is for looprow with label: ${MyLoop.current.label}</p>
      <p>This is for looprow with label: ${MyLoop.enabledRows[MyLoop.index].label}</p>
    </html>

  </block>
  <looprow label="1"><loopvar name="item">Item 1</loopvar></looprow>
  <looprow label="2"><loopvar name="item">Item 2</loopvar></looprow>
  <looprow label="3"><loopvar name="item">Item 3</loopvar></looprow>
  <looprow label="4"><loopvar name="item">Item 4</loopvar></looprow>
</loop>

Since randomizeChildren="1" is set on the <loop> element above, any of the rows could be seen first. Shown below is what would it would look like if the third row (label="3") was the first to be seen.

Given a loop with "MyLoop" specified as the label, here are several different variables that we have access to:

p.loopindex = Loop_Q1.index
rowCond="row.index != p.loopindex"
p.loopindex = Loop_Q1.index
rowCond="row.index != p.loopindex"
MyLoop.index
The index of the current cycle within the loop. Starting at 0, the index will increase for every <looprow> evaluated (e.g. 0, 1, 2, 3).

For example, if you wanted to display a intermission message on the fifth cycle of the loop, you could specify:
<html label="Intermission_[loopvar: label]" cond="MyLoop.index == 4">
 Almost done!
</html>

Currently there is a bug where using MyLoop.index in a cond="" or rowCond="" throws a fatal error.  As a work around set the loop index to a persistent variable then use that as shown below.

p.loopindex = Loop_Q1.index
rowCond="row.index != p.loopindex"
 
MyLoop.length
The total number of cycles that will be seen. Since conditions can be set on <looprow> elements, it's possible that not every <looprow> will be seen by each respondent. This number will reflect the exact number of <looprow> elements that will be evaluated.

For example, if we added a fifth <looprow> to the example above with cond="0" specified, MyLoop.length would still return 4.
 
MyLoop.enabledRows
This is a list of all <looprow> elements that will be evaluated. This means that len(MyLoop.enabledRows) == MyLoop.length is True. You can access the current <looprow> element's label with MyLoop.enabledRows[MyLoop.index].label.
 
MyLoop.current
This is the same as MyLoop.enabledRows[MyLoop.index], the current <looprow> object.
 
[loopvar: label]
This is the same as MyLoop.enabledRows[MyLoop.index].label.
 
[loopvar: VARIABLE]
This is the content specified in the <loopvar name="VARIABLE"> element of the current <looprow>.
 

4:  Examples

4.1:  Example: A Simple Loop

Loops are a great way to ask the same question(s) for multiple rows that were selected in a previous question.

<checkbox label="Q1" atleast="1">
  <title>Which of the following brands are you aware of?</title>
  <row label="r1">Brand #1</row>
  <row label="r2">Brand #2</row>
  <row label="r3">Brand #3</row>
  <row label="r4">Brand #4</row>
</checkbox>
<suspend/>

<loop label="Brands_Loop" vars="brand">
  <block label="Brands_Block" cond="Q1.r[loopvar: label]">

    <textarea label="Q2_[loopvar: label]">
      <title>What do you think about [loopvar: brand]?</title>
    </textarea>
    <suspend/>

    <radio label="Q3_[loopvar: label]" type="rating" values="order">
      <title>How would you rate [loopvar: brand]?</title>
      <col label="c1">1 - Bad</col>
      <col label="c2">2</col>
      <col label="c3">3</col>
      <col label="c4">4</col>
      <col label="c5">5 - Good</col>
    </radio>
    <suspend/>

  </block>
  <looprow label="1">
    <loopvar name="brand">Brand #1</loopvar>
  </looprow>
  <looprow label="2">
    <loopvar name="brand">Brand #2</loopvar>
  </looprow>
  <looprow label="3">
    <loopvar name="brand">Brand #3</loopvar>
  </looprow>
  <looprow label="4">
    <loopvar name="brand">Brand #4</loopvar>
  </looprow>
</loop>

In this example, two questions will be asked for every brand that is selected at question Q1. There is a single <looprow> element for every brand that contains a <loopvar> named "brand" containing the brand that will be piped into each question, Q2 and Q3.

To only show the brands that were selected at Q1, a condition was added to the main <block> element (e.g. cond="Q1.r[loopvar: label]"). If this condition is False, the entire section will be skipped and the questions will not be seen for that particular brand.

Conditions can be applied to <looprow> elements individually.

e.g. <looprow label="1" cond="Q1.r1">

Demo

Click here to see the code above in a live survey.

4.2:  Example: A Shuffled Loop

In the example below, only the brands that are selected at Q1 are shown in the loop. Additionally, Q1's rows are shuffled and the loop's order is set to display in the same order.

<checkbox label="Q1" atleast="1" shuffle="rows">
  <title>Which of the following brands are you aware of?</title>
  <row label="r1">Brand #1</row>
  <row label="r2">Brand #2</row>
  <row label="r3">Brand #3</row>
  <row label="r4">Brand #4</row>
</checkbox>
<suspend/>

<exec>
# SET LOOP SHUFFLE ORDER SAME AS Q1.rows
Brands_Loop_expanded.order = Q1.rows.order
</exec>

<loop label="Brands_Loop" vars="brand" randomizeChildren="1">
  <block label="Brands_Block" cond="Q1.r[loopvar: label]">

    <textarea label="Q2_[loopvar: label]">
      <title>What do you think about [loopvar: brand]?</title>
    </textarea>
    <suspend/>

    <radio label="Q3_[loopvar: label]" type="rating" values="order">
      <title>How would you rate [loopvar: brand]?</title>
      <col label="c1">1 - Bad</col>
      <col label="c2">2</col>
      <col label="c3">3</col>
      <col label="c4">4</col>
      <col label="c5">5 - Good</col>
    </radio>
    <suspend/>

  </block>
  <looprow label="1">
    <loopvar name="brand">Brand #1</loopvar>
  </looprow>
  <looprow label="2">
    <loopvar name="brand">Brand #2</loopvar>
  </looprow>
  <looprow label="3">
    <loopvar name="brand">Brand #3</loopvar>
  </looprow>
  <looprow label="4">
    <loopvar name="brand">Brand #4</loopvar>
  </looprow>
</loop>

As shown below, the loop's cycle order is set to randomize and the order is set within the <exec> block:

<exec>
# SET LOOP SHUFFLE ORDER SAME AS Q1.rows
Brands_Loop_expanded.order = Q1.rows.order
</exec>

<loop label="Brands_Loop" title="Brand-Related Questions" vars="brand" randomizeChildren="1">

The <loop> element's label is set to "Brands_Loop", so why did we use "Brands_Loop_expanded"? This is because the <loop> element is expanded into questions when it's loaded into the system. For example, the code above really looks like this when it's loaded into the system:

<block label="Brands_Loop_expanded" countVisibleOnly="1" randomizeChildren="1" uid="525">
  <block label="Brands_Loop_1_expanded" cond="Q1.r1" randomize="1" uid="526">
    <textarea
    label="Q2_1"
    uid="527">
      <title>What do you think about Brand #1?</title>
    </textarea>

    <suspend uid="531"/>

    <radio
    label="Q3_1"
    averages="cols"
    type="rating"
    uid="532"
    values="order">
      <title>How would you rate Brand #1?</title>
      <col label="c1" uid="533" value="1">1 - Bad</col>
      <col label="c2" uid="534" value="2">2</col>
      <col label="c3" uid="535" value="3">3</col>
      <col label="c4" uid="536" value="4">4</col>
      <col label="c5" uid="537" value="5">5 - Good</col>
      <net label="top2" indices="4,3" pos="below" uid="540" where="report,summary">Top 2</net>
      <net label="bot2" indices="0,1" uid="541" where="report,summary">Bottom 2</net>
    </radio>

    <suspend uid="542"/>

    <suspend uid="543"/>
  </block>

  <block label="Brands_Loop_2_expanded" cond="Q1.r2" randomize="1" uid="544">
    <textarea
    label="Q2_2"
    ...
    ...
    ...

To view the expanded version of your survey, run: here save -t survey/path

The <loop> element will be expanded into its full form as if the questions were individually written out for every cycle. Fortunately, this is all done behind-the-scenes and you'll never have to work with loops in their expanded form.

When it comes to accessing the shuffle order for loops, however, we must use LABEL_expanded.order.

4.3:  Example: A Loop for Testing Multiple Concepts

In this example, we'll show 3 concepts to each respondent in a random order. The shuffle order will be determined by the quota system and the concepts to be seen will consist of an image with a paragraph of text.

First, we'll setup the quota sheet to determine the shuffle order:

Quota Definitions Result

With this quota sheet in place, the shuffle order of each concept will be evenly distributed for all of the respondents.

Here are the concept images that we'll be showing:

Concept 1 Concept 2 Concept 3
pipe_concept_2.png pipe_concept_3.png

In the code below, we:

  1. Call the quota sheet, "concept_order"
  2. Track the concept shuffle order in a hidden question
  3. Set the shuffle order for the main loop, "Concept_Loop"
  4. Specify the concept format using a <res> tag
  5. Create the main loop to cycle through each concept

We made use of the built-in Python function, format.

<quota sheet="concept_order" />

<radio label="vConcept_Order" where="execute">
  <exec>
shuffle_order = []
for marker in p.markers:
    if marker.startswith("C_"):
        shuffle_order = [int(order) for order in marker.split("_")[1:]]
        break

for row in vConcept_Order.rows:
    row.val = shuffle_order[row.index] - 1

Concept_Loop_expanded.order = shuffle_order
  </exec>
  <title>Shuffle order of concepts</title>
  <row label="r1">Concept 1</row>
  <row label="r2">Concept 2</row>
  <row label="r3">Concept 3</row>
  <col label="c1">First</col>
  <col label="c2">Second</col>
  <col label="c3">Third</col>
</radio>
<suspend/>

<res label="concept">
    <p>Concept #{0}</p>
    <p><img src="[rel concept_{0}.png]" class="concept" /></p>
    <p>Description: {1}</p>
</res>

<loop label="Concept_Loop" vars="description">
  <block label="Concept_Block">

    <html label="Concept_[loopvar: label]" where="survey">
      ${res.concept.format("[loopvar: label]", "[loopvar: description]")}
    </html>

    <radio label="Q1_[loopvar: label]" optional="0">
      <title>Do you like concept [loopvar: label]?</title>
      <row label="r1">Yes</row>
      <row label="r2">No</row>
    </radio>

  </block>
  <looprow label="1">
    <loopvar name="description">Concept 1 is awesome!</loopvar>
  </looprow>
  <looprow label="2">
    <loopvar name="description">Concept 2 saves babies!</loopvar>
  </looprow>
  <looprow label="3">
    <loopvar name="description">Concept 3 is the right choice.</loopvar>
  </looprow>
</loop>

In the example above, each of the 3 concepts are shown to the respondents. Using the quota system, we made sure that the shuffle order is evenly distributed (e.g. the first concept is shown first the same number of times as the second and third concepts, etc...). We used the <looprow> element's label to properly display the correct concept image and the <loopvar name="description"> to pipe in a unique description for each concept.

The code above produces the following result when the marker "C_3_2_1" has been set (pause to view the animation):

concepttest_loop.gif

Demo

Click here to view the example above in a live survey.

Turn on QA codes to see the quota choice and hidden question.

4.4:  Example: Conditionally Showing Elements Based on Looprow

The looprows attribute can be applied to question and cell elements within a loop to only shown those elements given that the current the label of the current <looprow> exists in the list.

For example:

<loop label="My_Loop" vars="item">
  <block label="My_Loop_Stage">

    <radio label="Q1_[loopvar: label]" looprows="1,2">
      <title>Please select one that is [loopvar: item]:</title>
      <row label="r1" looprows="1,a">Item 1</row>
      <row label="r2" looprows="1,2,a">Item 2</row>
      <row label="r3" looprows="2,a">Item 3</row>
    </radio>

    <html label="Outtro_[loopvar: label]" where="survey" looprows="a">
      That's the end of the loop.. Now we're going to ask you about...
    </html>

  </block>
  <looprow label="1"><loopvar name="item">awesome</loopvar></looprow>
  <looprow label="2"><loopvar name="item">super cool</loopvar></looprow>
  <looprow label="a"><loopvar name="item">your favorite</loopvar></looprow>
</loop>

In the code above, the question "Q1" is only shown for <looprow> elements with the label "1" or "2". The question's <row> elements are also conditionally shown based on the current <looprow> being shown.

The third <looprow> in the loop above will only show the <html> comment element and not the "Q1" question.

4.5:  Example: A Nested Loop 

You can also nest loops within other loops for further randomization and extended functionality. To nest a loop within another loop, you can add the target attribute to each loopvar to pipe in values specific to your new looprows:

<loopvar target="xxxx" name="xxxx">xxxx</loopvar>

If you would like to restrict how your looprows appear, you can add the looprows attribute to specify when an inner looprow should appear in an outer loop:

<looprow looprows="xxxx" label="xxxx">

In the example below, up to 2 selected vehicle categories will be shown from Q1 and Q3 brands. Additionally, vehicle categories from "r1" are anchored in place, and the lv2 loop will only show for Sedans/SUVs.

<checkbox
label="q1"
atleast="1">
<title>Vehicle Categories</title>
<comment>select all that apply</comment>
<row label="r1">Mid-Size Sedans</row>
<row label="r2">Crossovers</row>
<row label="r3">SUVs</row>
</checkbox>
<suspend/>
<loop label="l1" builderSource="q1" count="2" randomizeChildren="1" vars="lv1">
 <title>Brand comparison</title>
 <block label="b1" builder:title="default loop block">
   <radio
   label="q2_[loopvar:label]"
   optional="0"
   randomize="0">
     <title>How do you feel about [loopvar: lv1]?</title>
     <comment>select one</comment>
     <row label="r1" value="5">5</row>
     <row label="r2" value="4">4</row>
     <row label="r3" value="3">3</row>
     <row label="r4" value="2">2</row>
     <row label="r5" value="1">1</row>
   </radio>
   <loop label="l2" builderSource="q1" count="3" randomizeChildren="1" vars="lv2,lv3">
     <title>Brand comparison</title>
     <block label="b2" builder:title="default loop block">
       <radio
     label="q3_[loopvar: label]"
     optional="0"
     randomize="0">
         <title>Is the [loopvar: lv2] [loopvar: lv3], one of the vehicles you're considering?</title>
         <comment>select one</comment>
         <row label="r1">Yes</row>
         <row label="r2">No</row>
       </radio>
     </block>
     <looprow looprows="r1,r3" label="r4">
       <loopvar name="lv2">Toyota</loopvar>
       <loopvar target="r1" name="lv3">Camry</loopvar>
       <loopvar target="r2" name="lv3">NONE</loopvar>
       <loopvar target="r3" name="lv3">Highlander</loopvar>
     </looprow>
     <looprow label="r5">
       <loopvar name="lv2">Honda</loopvar>
       <loopvar target="r1" name="lv3">Accord</loopvar>
       <loopvar target="r2" name="lv3">CR-V</loopvar>
       <loopvar target="r3" name="lv3">Pilot</loopvar>
     </looprow>
     <looprow label="r6">
       <loopvar name="lv2">Mazda</loopvar>
       <loopvar target="r1" name="lv3">Mazda6</loopvar>
       <loopvar target="r2" name="lv3">CX-5</loopvar>
       <loopvar target="r3" name="lv3">CX-9</loopvar>
     </looprow>
   </loop>
 </block>
 <looprow label="r1" cond="(q1.r1)" randomize="0">
   <loopvar name="lv1">Mid-Size Sedans</loopvar>
 </looprow>
 <looprow label="r2" cond="(q1.r2)">
   <loopvar name="lv1">Crossovers</loopvar>
 </looprow>
 <looprow label="r3" cond="(q1.r3)">
   <loopvar name="lv1">SUVs</loopvar>
 </looprow>
</loop>

Note: adding looprows will allow you to exclude/specify inner looprows with regard to the outer looprows.

5:  Loop Data Outputs 

Projects that contain a nested loop will have additional “Stacked Data” download options with Tab-Delimited and Fixed-Width (unicode) formats. A Stacked Data zip file will contain the following for each segment/split:

  • Unstacked data - a default output file with all variables that are not in loops.
  • Stacked data - a file with data stacked once per loop. This file has the loop label suffixed to it.
  • Datamap - two files: one for the unstacked data layout and one with the layout per loop.

Example 

If survey 123 has two segments: "Male" and "Female", and two loops: loop1 and loop2, the resulting Stacked Data zip file will contain 6 data files:

  • Male/123.txt                         Male data not in any loops
  • Male/123_loop1.txt             Male loop data for "loop1"
  • Male/123_loop2.txt             Male loop data for "loop2"
  • Female/123.txt                     Female data not in any loops
  • Female/123_loop1.txt         Female loop data for "loop1"
  • Female/123_loop2.txt         Female loop data for "loop2"

Tip: For users with Cloud Access, nested loop data is also available using the command line and the Decipher API.

6:  What's Next?

Learn more about creating sections and shuffling questions with the Block Tag.