Conditional "FollowOns" question

Issue with the below code. I am trying to include a “followOns” conditional question if a certain response is included in the selection.

We basically have a checkbox response area on the first question, and if the response includes a particular response variable (i.e. Heavy weapons) I want the program to ask the follow up question. However the conditional “FollowOns” does not seem to recognize the “CheckboxResponseArea” as it does the “MultipleChoiceResponseArea” and when demoing on TabSINT, overlooks the “FollowOns” question.

I have used the TabSINT online user guide (https://tabsint.org/docs/user-guide/advanced-protocols.html#follow-ons) but am still coming up short. Would you be able to advise?

{
  "id": "ft_qstnr_q9",
  "skipIf": "flags.ft_qstnr_q8_never||flags.ft_qstnr_q8b_combat||flags.ft_qstnr_q8b_offduty",
  "questionMainText": "What was the noise exposure source in military training? (Select all that apply)",
  "responseArea": {
    "type": "checkboxResponseArea",
    "choices": [
      {
        "id": "1",
        "text": "Exposure to noise from a vehicle (Tank, Truck, Helicopter, Ship, etc.)"
      },
      {
        "id": "2",
        "text": "Exposure to noise from other machinery"
      },
      {
        "id": "3",
        "text": "Exposure to small arms fire (<12.7 mm)"
      },
      {
        "id": "4",
        "text": "Exposure to heavy weapons fire (artillery, mortar, etc.)"
      },
      {
        "id": "5",
        "text": "Exposure to grenades"
      },
      {
        "id": "6",
        "text": "Exposure to other explosives (demolition)"
      },
      {
        "id": "7",
        "text": "Other"
      }
    ],
    "verticalSpacing": 20
  },
  "setFlags": [
    {
      "conditional": "result.response==4",
      "id": "ft_qstnr_q9_hvyweapons"
    }
  ],
  "followOns": [
    {
      "conditional": "result.response==4",
      "target": {
        "id": "ft_qstnr_q9a_hvyweapons",
        "questionMainText": "What kind of heavy weapon?",
        "responseArea": {
          "type": "checkboxResponseArea",
          "choices": [
            {
              "id": "Machine gun or large caliber firearm"
            },
            {
              "id": "Mortar"
            },
            {
              "id": "Artillary"
            },
            {
              "id": "Shoulder-fired rocket"
            },
            {
              "id": "Other"
            }
          ]
        }
      }
    }
  ]
}

@Devon Thanks for the question.

The conditional statement in the “followOn” doesn’t work because the CheckboxResponseArea allows for multiple selections. The result.response value following a CheckboxResponseArea is an array.

For your followOn to work, use the following conditional:

"followOns": [
    {
      "conditional": "result.response.includes('4')”,
    ...

You should also change the conditional in the "setFlags" array:

  "setFlags": [
    {
      "conditional": "result.response.includes('4')”,
      "id": "ft_qstnr_q9_hvyweapons"
    }
  ],

Hello @marc,

I have a related issue to conditional statements for flags set based on responses to a CheckboxResponseArea. I will include a short protocol to demonstrate the issue at the end. The problem is that when using the.includes() method, some flags might be unintentionally set because .includes("1") and .includes("0") are both true for a results.response that contains “10” but not specifically “1” nor “0”. It appears that, at least when the eval is performed on the conditional for the flag, result.response is behaving as a string rather than an array. Something like "[\"10\"]" or "["10"]" rather than, as I had expected ["10"]. Generally, I would guess this is an issue any time there is a response id being checked using .includes() and that id is a substring of another response id.

Here is a protocol where selecting the item with id 10 (K) will result in flags being set as if the items for id 0 (A) and id 1 (B) were selected. Similarly, selecting the item with id 11 (L) will result in flags being set as if the item for id 1 (B) was selected, whether or not it actually was.

I am working around it now but I thought I’d raise it as a possible issue because it caused some unexpected behavior for protocols I am working with. I am using tabSINT version 3.1.0 on Samsung-SM-T377A running on Android 6.0.1.

Thanks for your time,
Trevor

{
"title": "Main",
"protocolId": "Main",
"hideProgressBar": 1,
"enableBackButton": 1,
"pages": [
    {
        "id": "main1",
        "questionMainText": "Choose one or more",
        "responseArea": {
            "type": "checkboxResponseArea",
            "choices": [
                            {
                    "id": "0",
                    "text": "A"
                },
                            {
                    "id": "1",
                    "text": "B"
                },
                            {
                    "id": "2",
                    "text": "C"
                },
                            {
                    "id": "3",
                    "text": "D"
                },
                            {
                    "id": "4",
                    "text": "E"
                },
                            {
                    "id": "5",
                    "text": "F"
                },
                            {
                    "id": "6",
                    "text": "G"
                },
                            {
                    "id": "7",
                    "text": "H"
                },
                            {
                    "id": "8",
                    "text": "I"
                },
                            {
                    "id": "9",
                    "text": "J"
                },
                            {
                    "id": "10",
                    "text": "K"
                },
                            {
                    "id": "11",
                    "text": "L"
                }
            ],
            "verticalSpacing": 20,
            "responseRequired": 1
        },
        "setFlags": [
                    {
                "conditional": "result.response.includes(\"0\")",
                "id": "a"
            },
                    {
                "conditional": "result.response.includes(\"1\")",
                "id": "b"
            },
                    {
                "conditional": "result.response.includes(\"2\")",
                "id": "c"
            },
                    {
                "conditional": "result.response.includes(\"3\")",
                "id": "d"
            },
                    {
                "conditional": "result.response.includes(\"4\")",
                "id": "e"
            },
                    {
                "conditional": "result.response.includes(\"5\")",
                "id": "f"
            },
                    {
                "conditional": "result.response.includes(\"6\")",
                "id": "g"
            },
                    {
                "conditional": "result.response.includes(\"7\")",
                "id": "h"
            },
                    {
                "conditional": "result.response.includes(\"8\")",
                "id": "i"
            },
                    {
                "conditional": "result.response.includes(\"9\")",
                "id": "j"
            },
                    {
                "conditional": "result.response.includes(\"10\")",
                "id": "k"
            },
                    {
                "conditional": "result.response.includes(\"11\")",
                "id": "l"
            }
        ]
    },
    {
        "id": "main2",
        "questionMainText": "You picked A",
        "skipIf": "!flags.a"
    },
    {
        "id": "main3",
        "questionMainText": "You picked B",
        "skipIf": "!flags.b"
    },
    {
        "id": "main4",
        "questionMainText": "You picked K",
        "skipIf": "!flags.k"
    },
    {
        "id": "main5",
        "questionMainText": "You picked L",
        "skipIf": "!flags.l"
    },
    {
        "id": "main6",
        "questionMainText": "You picked something else",
        "skipIf": "flags.a|| flags.b || flags.l ||  flags.k"
    }
]

}

I found the solution in the Advanced Protocols help page: https://tabsint.org/docs/user-guide/advanced-protocols.html

arrayContains(strArray, item) : Converts a JSON string array (such as that stored by a checkBoxResponseArea) to a Javascript array, and then checks whether item is in it.

I am using that now and it works great!

@ttp , that is a good point. It sounds like you have a workaround, but I figured I’d comment to provide some additional background. We store responses as JSON serialized strings, along with all other exam results. As such, they need to be deserialized in order to access the raw objects. This means you could also use a conditional like:

"conditional": "angular.fromJson(result.response).includes('0')"

However, we may want to reconsider how these conditionals are reevaluated, so I’ve created an issue on Gitlab, and may include an update to our evalConditional method of exam.js in an upcoming release.

Note, we’ve started adding a section to TabSINT’s response area user guide to highlight the kinds of nuances brought up in this forum post. For example, refer to the response section of the checkboxResponseArea documentation.

I’m revisiting this thread with another question. I have a example of a checkboxResponseArea with 5 choices (‘A’, ‘B’, ‘C’, ‘D’, ‘E’), with 3 correct responses (‘A’, ‘C’, ‘D’). If the user doesn’t select all 3 correct responses, they would be directed to the next page as a followOn.

I haven’t run through the protocol to check but my thought is that using conditional of “result.response.includes” with the 2 incorrect responses wouldn’t be accurate in the case of the person selecting, for example, 2 of the 3 correct answers or something similar.

Instead of setting up the follow up as:

"followOns":  [
{
"conditional": "results.response.includes('...')"

could I set it up with:
"followOns": [
{
"skipIf": "flags.result.response.includes('A')| flags.result.response.includes('C')|flags.result.response.includes('D')",

In short, I’m asking how to set up a required followup up question that can only be skipped if and only if all 3 responses are selected.

Thank you.

@Devon

I would also want to write out your protocol, then test the functionality to be sure, but I believe you do want to use the skipIf flag. In fact, I think you can simplify it a bit. Here is some pseudocode:

"setFlags": [
    {
        "conditional": "angular.isDefined(result.response) && angular.fromJson(result.response).includes('A') && angular.fromJson(result.response).includes('B') && angular.fromJson(result.response).includes('C') && angular.fromJson(result.response).length === 3"
        "id": "correct"
    }
],
...
{
        // followOn question to skip
        "skipIf": "flags.correct"
}

FollowOn components do not use the skipIf option, per the schema. However, they do have the conditional flag, which can serve the same purpose by using the inverse value of the desired skipIf. So, if you have a flag ("id": "allCorrect") that indicates whether the response has all correct and only correct answers, and want to skip the followOn if that is true, set the conditional to: "!flag.allCorrect".