TabSINT

TabSINT

  • Docs
  • Forum
  • Releases
  • Changelog
  • About

›User Guide

Quick Start

  • TabSINT Quick Start
  • WAHTS Quick Start
  • Run an Exam

User Guide

  • Introduction
  • Background
  • Tablet Setup
  • Configuration
  • Data Interface
  • Results Analysis
  • WAHTS
  • Dosimeter
  • Protocols
  • Response Areas
  • WAHTS Response Areas
  • Advanced Protocols
  • Example Protocols
  • Generate Configuration Code
  • FAQ

References

  • References

Advanced Protocols

Protocols can support many advanced features, including custom navigation menus, dynamically calculated page properties, and complex logic flow.

These sections document the more advanced protocol features in TabSINT.

Navigation Options

Protocol flow can be further customized using navigation menus and back buttons.

Navigation Menu

The navigation drop-down menu in the top right of the tablet can be customized during exams using the navMenu object in a protocol.

"navMenu":[
  {
    "text": "Back to Main Menu",
    "target": {
      "reference": "MainMenu"
    },
    "returnHereAfterward": false
  }
]

All three fields of the navMenu object are required.

The link text and target are set using text and target, respectively. The field returnHereAfterward allows the link to behave in two different ways:

  • false: Replace all currently queued pages with the target, finish exam when target is complete.
  • true: Add the linked section to the current protocol stack. The page displayed when the link is pressed will be shown after the target is complete.

Dynamic Logic

TabSINT has many features to support developing dynamic, adaptive questionnaires. There following section describes the many ways to implement dynamic logic and content.

Custom Expressions

Custom expressions are used to define logic that can be used to create adaptive protocols.

The following section will describe how to define custom expressions. The sections after will describe how to use these expressions to define page logic.

Warning: Custom expressions are difficult to use and debug, please use with caution and only when necessary.

Syntax

Custom expressions are written using a safe subset of the Javascript programming language. The vast majority of Javascript expressions are legal in TabSINT.

Specifically, TabSINT uses AngularJS's $eval(...) function to evaluate expressions because it is relatively safe, secure, and flexible.

For more information, see:

  • A brief guide to Javascript Syntax

  • A Javascript Tutorial

  • Specific details regarding AngularJS's expression syntax

Namespace

In custom expressions, you can reference the following functions and variables:

Libraries and Convenience Functions:

  • Most native Javascript functions (see AngularJS's $eval for specifics).

  • The javascript Math library: standard math functions including:

    • Math.abs for absolute value
    • Math.min for minimum of two numbers
    • Math.max for maximum of two numbers
  • The Lodash Javascript Library: Provides numerous useful convenience functions and functional programming tools, such as:

    • _.filter() to restrict items in a list based on some criteria
    • _.countBy() to count items based on some criteria
    • _.shuffle() to randomize a list
    • _.map(), _.reduce(), _.collect(), and numerous other important functional programming tools
  • Custom TabSINT Functions, written solely to make these custom expressions shorter and more semantic:
    • 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.

Exam Results and Exam State

  • flags: A javascript object containing a copy of all set flags. Refer to it using dot notation, e.g., flags.q1Answered for a flag named q1Answered.

  • result: A copy of the response from the previous (most recent) question.

  • examResults: A copy of the exam's entire results structure, similar to that downloaded for post-analysis. It contains the following fields:

    • protocolName: Name of the active protocol
    • qrString: QR Code, if the protocol includes a qrResponseArea
    • testDateTime: For example, '2014-07-07T15:55:30.942Z'
    • testResults: An array of test results objects
    • subjectId: Subject id for the current exam, if a prior subject ID response area is present

Examples

MeaningCode
If the previous question was correctresult.correct
If the previous result was incorrect!result.correct
If the previous response was "dog"result.response === 'dog'
If the previous response was 34 and the usesHearingProtection flag is set.(result.response === '34') && flags.usesHearingProtection
If the subject chose both earPlugs and other:arrayContains(result.response, 'earPlugs') && arrayContains(result.response, 'other')

Flags

Flags can be set at the end of each page based on the user response. The flag can then be used in a custom expression to control the logic flow of the test at any later point in time.

"pages":[
  {
    "id":"question1",
    "questionMainText":"How many years of service do you have?",
    "responseArea":{
      "type":"integerResponseArea"
    },
    "setFlags":[
      {
        "id": "integerFlag"
        "conditional":"result.response>5"
      }
    ]
  }
]

Repeats

Repeats allow you to show the same question again, based on a conditional custom expression.

"pages":[
  ...
  {
    "repeatPage":{
      "nRepeats":2,
      "repeatIf":"result.response !== 'B'"
    }
  }
]

Note the question will be repeated up to 2 times (max 3 repeats) if the participant continues to answer A.

If repeatIf is undefined, the page will repeat nRepeats times.

See Repeats Example for an example protocol.

Follow Ons

FollowOns are useful when one or more immediate follow-up questions should be asked if the response to the current question meets some criteria.

{
  "id":"question1",
  "questionMainText":"How many years of service do you have?",
  "responseArea":{
    "type":"integerResponseArea"
  },
  "followOns":[
    {
       "conditional":"result.response > 5",
       "target":{
          "id":"question2",
          "questionMainText":"How many times have you been deployed to Iraq or Afghanistan?",
          "responseArea":{
             "type":"integerResponseArea"
          }
       }
    }
  ]
}

The second question will only be asked if the subject answers that they have more than 5 years of service.

The conditional is implemented as a custom expression and must return a boolean (true or false). If the conditional returns true, the target is executed. If the conditional returns false, the target is ignored.

Multiple sets of conditionals and targets can be included in a single instance of followOns.
Each target can be defined as a single page or a reference to a subprotocol.

Skip If

Pages can be skipped using the skipIf object. This object is evaluated prior to the page being rendered.

The custom expression used in this object can leverage the result of the previous question or any previously set flags.

"pages":[
  {
    "id":"question1",
    "questionMainText":"How many years of service do you have?",
    "responseArea":{
      "type":"integerResponseArea"
    }
  },
  {
    "id":"question2",
    "questionMainText":"This will be skipped if the previous response > 5",
    "responseArea":{
      "type":"integerResponseArea"
    },
    "skipIf": "result.response > 5"
  }
]

Feedback

Feedback options allow you to provide feedback on certain questions after the user submits an answer. The feedback field has two possible values:

  • gradeResponse: will mark answers correct (red) and incorrect (green).
  • showCorrect: will mark answers correct/incorrect AND reveal correct answers the user missed.
"responseArea":{
  ...
  "feedback": "gradeResponse"
}

See Feedback Example for an example protocol with feedback.

Special References

@PARTIAL

If an exam is terminated using the End Exam and Submit Partial Results link, the protocol can specify a final subprotocol to run before the test ends. This subprotocol must have the special reference ID @PARTIAL.

"subProtocols":[
  {
    "protocolId": "@PARTIAL",
    "title":"Final Section",
    "pages":[
      ...
    ]
  }
]

Potential use cases include:

  1. Display a page or sequence of pages asking for feedback on why the exam is being ended prematurely.
  2. Collect required information that would otherwise be collected at the end of the exam.

@END_ALL

The special reference @END_ALL will automatically end the test no matter where the user is in the protocols stack. This can be used to manually end a protocol early in a FollowOn, subprotocol, or other special circumstance.

{
  "id":"question1",
  "questionMainText":"How many years of service do you have?",
  "responseArea":{
    "type":"integerResponseArea"
  },
  "followOns":[
    {
      "conditional":"result.response > 5",
      "target":{
         "reference": "@END_ALL"
       }
    }
  ]
}

Examples

Kitchen Sink

This example shows many of the available features for implementing dynamic questionnaires.

{
   "title":"A Brief Example of Questionnaire Attributes",
   "pages":[
      {
         "id":"question1",
         "questionMainText":"What is your age?",
         "responseArea":{
            "type":"integerResponseArea"
         },
         "followOns":[
            {
               "conditional":"result.response>=21",
               "target":{
                  "id":"question1a",
                  "questionMainText":"Do you like to have beer or wine with your evening meal?",
                  "responseArea":{
                     "type":"yesNoResponseArea"
                  }
               }
            }
         ]
      },
      {
         "id":"question2",
         "questionMainText":"What is your favorite color?",
         "responseArea":{
            "type":"multipleChoiceResponseArea",
            "choices":[
               {
                  "id":"Red"
               },
               {
                  "id":"Green"
               },
               {
                  "id":"Blue"
               }
            ]
         },
         "setFlags":[
            {
               "conditional":"result.response=='Red'",
               "id":"likesRed"
            }
         ]
      },
      {
         "id":"question3",
         "questionMainText":"How frequently do you read for pleasure?",
         "responseArea":{
            "type":"likertResponseArea",
            "levels":5,
            "specifiers":[
               {
                  "level":0,
                  "label":"Never"
               },
               {
                  "level":2,
                  "label":"Occasionally"
               },
               {
                  "level":4,
                  "label":"Every day"
               }
            ]
         },
         "followOns":[
            {
               "conditional":"result.response>=2",
               "target":{
                  "reference":"reader"
               }
            }
         ]
      },
      {
         "id":"question4",
         "skipIf":"flags.likesRed",
         "questionMainText":"Do you dislike the color red?",
         "responseArea":{
            "type":"yesNoResponseArea"
         }
      }
   ],
   "subProtocols":[
      {
         "protocolId":"reader",
         "title":"Reader Survey",
         "pages":[
            {
               "id":"questionR1",
               "questionMainText":"What type of reading do you enjoy?",
               "responseArea":{
                  "type":"checkboxResponseArea",
                  "choices":[
                     {
                        "id":"Novels"
                     },
                     {
                        "id":"Biography"
                     },
                     {
                        "id":"Nonfiction"
                     },
                     {
                        "id":"News"
                     }
                  ],
                "other":"Other"
               }
            }
         ]
      }
   ]
}

Running Subprotocols

This example shows how to randomly run one out of several available subprotocols.

{
  "title":"Example: Running One of Several Subprotocols",
  "randomization":"WithoutReplacement",
  "timeout":{
    "nMaxPages":1
  },
  "pages":[
    {
      "reference":"sub1"
    },
    {
      "reference":"sub2"
    }
  ],
  "subProtocols":[
    {
      "protocolId":"sub1",
      "title":"Subprotocol #1",
      "pages":[
        {
          "id": "info001",
          "questionMainText": "You are in subprotocol #1."
        },
        {
          "id": "info002",
          "questionMainText": "You are leaving subprotocol #1."
        }
      ]
    },
    {
      "protocolId":"sub2",
      "title":"Subprotocol #2",
      "pages":[
        {
          "id": "info001",
          "questionMainText": "You are in subprotocol #2."
        },
        {
          "id": "info002",
          "questionMainText": "You are leaving subprotocol #2."
        }
      ]
    }
  ]
}

Preprocess Function

The page preProcessFunction allows more advanced adaptive logic implementation BEFORE each page is displayed. These functions can be used to adaptively modify any page property, including question texts, followOns, question type, flags, or the progress bar value.

Implementation Overview

  • Create a function that calculates any new or modified page properties and returns an object with just those modified properties.
  • If a page references that function (by name) as its preProcessFunction, then TabSINT runs the function and alters the 'page' specification before it's displayed.
  • Any changes to the page are stored with the exam results for that page, so that during post-processing it is clear exactly what page was presented.

Note that the the preprocessing function only needs to specify the fields that need to be changed; these fields are updated, and all other fields on the page remain the same.

Required Code

The minimum required code for a dynamic function is:

customJs.js

(function() {

  tabsint.register('functionName', function(dm){
    var returnObject;

    // logic, use dm fields such as dm.result, read/write flags, etc.

    return returnObject;// returned fields, if any, will overwrite or add to current page fields
  };

})();

The controller must be registered using the global TabSINT service tabsint.register('functionName', function() {}).

protocol.json

"pages":[
  {
    "id":"multichoice001",
    ...
    "preProcessFunction": "functionName"
  }
]

Objects and Data Available to Dynamic Functions

The following functions and objects can be accessed via the global namespace (Math, _, etc) or an optional input variable (dm above) in a dynamic function. For example, dm.page accesses the current page object.

Libraries and Convenience Functions:

  • Most native Javascript functions (see AngularJS's $eval for specifics).

  • The javascript Math library: standard math functions including:

    • Math.abs() for absolute value
    • Math.min() for minimum of two numbers
    • Math.max() for maximum of two numbers
  • The Lodash Javascript Library: Provides numerous useful convenience functions and functional programming tools, such as:

    • _.filter() to restrict items in a list based on some criteria
    • _.countBy() to count items based on some criteria
    • _.shuffle() to randomize a list
    • _.map(), _.reduce(), _.collect(), and numerous other important functional programming tools

Exam Results (read-only)

Assuming you use dm as the input variable to your function, as in the example above:

  • dm.result: A copy of the response from the previous (most recent) question.

  • dm.examResults: A copy of the exam's entire results structure, similar to that downloaded for post-analysis. It contains the following fields:

    • dm.examResults.protocolHash

    • dm.examResults.protocolId

    • dm.examResults.qrString

    • dm.examResults.siteId

    • dm.examResults.testDateTime: for example, '2014-07-07T15:55:30.942Z'

    • dm.examResults.testResults: An array of test results objects

      • dm.examResults.testResults.responses: An array of response fields, including correct, eachCorrect, otherResponse, presentationId, response, responseElapTimeMS, responseStartTime

Modifiable State Objects (read/write)

  • dm.flags: All set flags. Refer to flags fields using dot notation, e.g., dm.flags.q1Answered for a flag named q1Answered.

Page Fields (read-only)

  • dm.page: The current page, including all page fields established by the protocol.json.

Dynamically Altering Flags

Flags can be changed directly and can be used to store data or to pass data from page to page. Flags are reset at the beginning of each exam. Fields are accessed and created using the 'dot' notation, i.e. dm.flags.myVar = 2;.

Dynamically Changing Page Fields

The page field is read-only and cannot be altered directly. To modify page fields, return an object with a structure following the structure in the JSON Schema. Page field changes will be appended to the results structure for each page, to document what was changed.

var retObject = {
  pageFieldToChange: newValue,
  questionMainText: newTextValue,
  progressBarVal: newProgressVal
  ...
};
return retObject;

It is important to note that objects (typically defined with curly braces {}) only need to contain the changed fields, and that TabSINT does its best to deal intelligently with nested changes.

However, TabSINT completely replaces arrays (typically defined with square []).

For example, to change questionMainText, which is a direct child of page, we simply return {questionMainText: newTextVariable} but for choices, which is a nested child of responseArea, we must return {responseArea: {choices: newChoices}} for the change to be correctly placed. Other fields, such as `{responseArea: {type:...}}' will be unaffected.

Note also that choices is an ARRAY according to the JSON Schema. To add/change an element in an array, save the current array to a new variable, add/change the element of interest, then return the updated array.

Example: Using Dynamic Functions to Modify Page Properties

Take for example, the progressBarVal, documented as a page field in the JSON Schema. Let's say you wanted to calculate the progress bar value using your own custom function, 'calculateProgress'. This is how you would include your function in the protocol.json file using the preProcessFunction field:

{
  "title":"A Simple Exam With a Custom Function to Set the Progress Bar",
  "pages":[
    {
      "id":"multichoice001",
      "title":"Multiple Choice 1",
        "questionMainText":"Sample question.",
        "responseArea":{
          "type":"yesnoResponseArea"
        },
      "preProcessFunction": "calculateProgress"
    }
  ]
}

And how you would set the function in 'customJs.js', a single javascript file included with your protocol zip:

(function() {

  tabsint.register('calculateProgress', function(dm) {

    var response = dm.result.response;
    var length = dm.examResults.responses.length;
    var progress;
    if (response === 'y') {
      progress = 'Question '+length+'\/5 in Section 4';// escaped forward slash
    } else {
      progress = 100 * length / 10;
    }
    
    // Return the proper object structure. In this case, we replace
    // 'progressBarVal' with a new value ('progress'). This change
    // will be recorded in the exam results for the current page.
    var retObject = {
      progressBarVal: progress
    };

    return retObject;
  });

});

When page 'multichoice001' is loaded, the calculateProgress function will run and modify the progressBarVal

Example: Additional Page Property Modifications Using Dynamic Functions

(function() {

  tabsint.register('changeText', function(dm) {
    var response = dm.result.response;
    var newQuestionMainText;
    var newChoices;

    if (response === 'A'){
      newQuestionMainText = 'Do you like baseball?';
      newChoices = [{id:'A',text:'Yes I like Baseball'},{id:'B',text:'No, I do not like Baseball'}]
    } else if (response === 'B'){
      newQuestionMainText = 'Do you like cars?';
      newChoices = [{id:'A',text:'Yes I like Mustangs'},{id:'B',text:'No, I ride my bike'}]
    }

    return {
      questionMainText: newQuestionMainText,
      responseArea: {
        choices: newChoices}
    };
  });

  tabsint.register('addFollowOn', function(dm) {
    // add a followOn and conditional flag

    var newSetFlags = [
      {
        id:'DO_FOLLOW_ON',
        conditional:"result.response === 'y'"
      }
    ];

    var newFollowOns = [
      {
        conditional:'flags.DO_FOLLOW_ON',
        target:{
          id:'ynFollowOn',
          questionMainText:'Are you enjoying this follow-on?',
          wavfiles:[
            {
              path:'chirpFullScaleWRTRef.wav',
              targetSPL:'80'
            }
          ],
          responseArea:{
            type:'yesNoResponseArea'
          }
        }
      }
    ];

    return {
      followOns: newFollowOns,
      setFlags: newSetFlags
    };
  });

})();

Custom Response Areas

Advanced users can include custom response areas that extend the standard TabSINT functionality.

These pages can be used for an additional type of response area or to analyze and display results. See the Protocol Development Tools section of the Developer Guide for more information about using custom response areas.

Warning: These response areas are difficult to write and debug properly. Creare can develop custom response areas, which can then be extended or customized. This functionality is exposed for advanced users only. Effective protocol development requires, at a minimum, familiarity with:

  • Angularjs
  • TabSINT
  • The Lodash Javascript Library
  • Bootstrap and AngularUI

Subject History

TabSINT can keep track of individual subjects taking an exam on a site. A subject's results from a previous exam can be used to inform the content of that subject's exam at a later date.

Utilizing subject history requires three individual parts:

  • A Subject ID Response Area to attach a subject id to exam results

  • A server side exam results processing function

  • Protocol-level logic to use the subject history

Subject ID Response Area

The subject ID response area will attach a unique subject id to exam results for a specific site.

The subject ID response area is defined in the json schema as:

"subjectIdResponseArea":{
        "description":"A response area to record a subject id in the exam results",
        "properties":{
                "type":{
                        "enum":[
                                "subjectIdResponseArea"
                        ]
                },
                "skip": {
                        "type": "boolean",
                        "description": "Allow user to skip entering the subject id"
                }
        }
}

Using Subject History in a protocol

If subject history information is available for a site, it will be put on the flags object of any protocol running on that site. See Implementing Logic for details on how to access these objects and use them in a protocol.

An example protocol using subject history is shown in Subject History Example

Last updated on 3/9/2022
← WAHTS Response AreasExample Protocols →
  • Navigation Options
    • Navigation Menu
  • Dynamic Logic
    • Custom Expressions
    • Flags
    • Repeats
    • Follow Ons
    • Skip If
    • Feedback
    • Special References
  • Examples
    • Kitchen Sink
    • Running Subprotocols
  • Preprocess Function
    • Implementation Overview
    • Required Code
    • Objects and Data Available to Dynamic Functions
    • Dynamically Altering Flags
    • Dynamically Changing Page Fields
    • Example: Using Dynamic Functions to Modify Page Properties
    • Example: Additional Page Property Modifications Using Dynamic Functions
  • Custom Response Areas
  • Subject History
    • Subject ID Response Area
    • Using Subject History in a protocol
TabSINT
Docs
IntroductionQuick StartUser Guide
Source
GitLabIssue Tracker
Community
YouTube
Copyright © 2023 Creare