Tutorial 202 - Displaying Distractor Rationale

Learn how to show feedback at the Question and/or response level. 

Note: there are two possible approaches to follow with showing distractor rationale:

  • Approach 1, the simplest way, using convenient, built-in init options and public methods, or 
  • Approach 2, an advanced way to achieve the same effect using the Learnosity SDK, signing an assessment request, creating a host page, initializing the API, and then parsing the rationale when a Question is validated.


In both the Author site and embedded Question Editor, distractor rationale information can be entered in the metadata section, as shown in Figure 1. A general field is available for cases where only one rationale needs to be displayed (typically for Questions where a single answer is possible), and per-response rationale can be used in cases where Authors would want to display a different rationale for each answer (typically for Questions where more than one answer choice is provided).

How optional information like feedback, hints, or sample answers is displayed can vary significantly from developer to developer, and even from case to case. As such, Learnosity provides a simple, out-of-the-box way to display distractor rationale using initialization options and public methods to cover the most common options. It is also possible to build your own display using Learnosity's data infrastructure and APIs to parse this information at runtime, which allows maximum flexibility in how the distractor rationale should be shown to the student.

Note: This feature is available for Multiple Choice, Cloze with drop down, Cloze with drag & drop, and Cloze with text.

Figure 1

You can configure the preview of question level and response level distractor rationale for Authors. 

Approach 1


In order to make it easy for customers to display distractor rationale, beginning with version v2020.1.LTS we offer an out-of-box UI with commonly desired configuration options. When a Question is validated, i.e. when the “Check Answer” button is clicked, the validate() public method is called, or when a Question-response is rendered in “review” state, distractor rationale will be shown according to the given configuration. 

Questions API Activity Configuration (a.k.a. init options)

The show_distractor_rationale value is a boolean/object hybrid that allows basic use with boolean values, or advanced use with object values.

Basic use:

  • show_distractor_rationale: true  shows distractor rationale with default configuration.
  • show_distractor_rationale: false will not show any distractor rationale.

Advanced use:

"show_distractor_rationale": {
     "per_question": "incorrect",
     "per_response": "always"

 The allowed values of per_question are:

  • always
  • correct
  • incorrect (default)
  • never

The allowed values of per_response are:

  • always (default)
  • never

Both per_question and per_response properties are optional and will use their respective default values when omitted.

Validation error messages will be logged to the JavaScript console when an improper property or value is provided. 

The Question JSON metadata property distractor_rationale is a string. If this metadata is not present, or if its value is the empty string, it will behave as if "per_question": "never" was used. 

The Question JSON metadata property distractor_rationale_response_level is an array that is expected to have one entry for each option or response container. If this metadata is not present, or if its value is the empty string, it will behave as if "per_response": "never" was used. 

The response-level distractor rationale should be labelled with a corresponding   stem_numeration to the option or response container to which it relates. The provided values of stem_numeration are:

  • number
  • upper-alpha
  • lower-alpha

For the MCQ block type, the choice_label should be preferred over the   stem_numeration when they are inconsistent.


Public Methods 

The  validate() public method accepts a new showDistractorRationale argument, which behaves as below:
  • When validate() is called with no arguments, or with an argument that doesn’t include showDistractorRationale property, distractor rationale will be shown in accordance with the activity configuration for distractor rationale.
  • When validate() is called with an argument with a property, that property will be used instead of the activity configuration to determine how distractor rationale is displayed.
  • An object value for showDistractorRationale is supported with properties  perQuestion and perResponse, just like its Activity configuration namesake. (Please note that these are camelCase to match JavaScript conventions, whereas the init options are snake_case because that’s the standard we use there.)
  • When resetValidationUI() is called, or when a change is made to a Question’s response, distractor rationale will be hidden. (Calling validate({ showDistractorRationale: false}) will also hide distractor rationale, but keep the validation UI visible.)

Figure 2: Multiple Distractor Rationale per Question and per Response Level


Approach 2

Approach 2 covers a more advanced approach to this task. The finished code will show no additional explanation if the answer is correct, a rationale for each incorrect response when the Question contains distractor rationale at response Level, or a single rationale when the Question contains a global distractor rationale. In addition, in cases where more than one answer is required, but only a single correct answer has been supplied, the student will be prompted to review the response for completeness. 

This approach is created in a context where Authors always fill either the global distractor rationale or the distractor rationale at response Level.

Note: This tutorial assumes you are familiar with the Learnosity Items APIPHP, and jQuery.

Using the Learnosity SDK

Security and authentication required to use Learnosity APIs is best handled by our SDK. In Tutorial 102, we created a simple config file that includes your customer key, customer secret, and an allowlisted domain. It also includes the SDK autoloader to simplify using dependencies and reduce related errors. The autoloader will try to load undefined classes or interfaces, before throwing an error. Learnosity provides SDKs in a variety of languages, this tutorial uses the PHP version.

The authentication information in the config file is combined with the assessment request data below and signed by the SDK using an SHA256 hashing algorithm. Learnosity servers sign the incoming request the same way, and matching signatures ensure that the data has not been tampered with during transmission.

Note: This tutorial uses demo values for the consumer key and secret. In production, you must use your own consumer key and secret.

Signing the Assessment Request

The signing code, shown below, first includes the aforementioned config file that contains the customer authentication data, and creates aliases for the Init and Uuid classes to simplify their use.


 include_once 'config.php';

 use LearnositySdkRequestInit;
 use LearnositySdkUtilsUuid;

 $request = [
   'user_id'        => 'student_1234',
   'session_id'     => Uuid::generate(),
   'items'          => [
   'rendering_type' => 'inline',
   'type'           => 'submit_practice',
   'activity_id'    => 'tutorial_activity',
   'name'           => 'Distractor Rationale Example',
   'config'         => [
       'renderSubmitButton'  => true

 $Init = new Init('items', $security, $consumer_secret,
 $signedRequest = $Init->generate();

The Request Object then includes the necessary information to create the assessment:

  • user id: unique student id
  • session id: unique id for each new session (generated for this tutorial by the SDK’s UUID class), or existing id (retrieved from your own system) when resuming a session
  • items: array of Items included in the assessment
  • rendering type: assess (uses Learnosity’s Assess UI to render assessment) or inline (rendering each item in its own DOM element giving developer full control over layout)
  • type: local practice (validated locally) or submit practice (submitted to Learnosity servers to support additional features such as reporting)
  • activity id: reference used to group all sessions of this assessment together to support additional features such as reporting
  • name: display name for assessment
  • config: object used for optional config settings. In this case, we will be rendering the submit button. This is handled automatically by the Assess rendering type, but is optional for inline to allow developers to use their own submit button, if desired.

This information is then combined with the authentication data and the requested API (line 23) to create a signed request (line 24).


Creating the Host Page 

The HTML used in this tutorial to render the assessment is very basic. Shown below, it starts with a few simple styles to clearly separate each item from the page background, and each distractor rationale from its corresponding Item. (As the distractor rationale will only be displayed when responses are incorrect or incomplete, a red alert/incorrect theme is used.) The body then includes a header, a DOM element for each Item in the assessment, and a submit button.

<!--php goes here-->

<!DOCTYPE html>
<html lang="en">
      <meta charset="utf-8">
      <title>Distractor Rationale</title>
        body {
        .learnosity-item {
          margin:20px 0px;
        .distractor {
      <h1>Distractor Rationale Tutorial</h1>
      <div style="width:750px;">
        <span class="learnosity-item" data-reference="act1"></span>
        <span class="learnosity-item" data-reference="act2"></span>
        <span class="learnosity-item" data-reference="act3"></span>
        <span class="learnosity-item" data-reference="act4"></span>
        <span class="learnosity-item" data-reference="act5"></span>
        <span class="learnosity-item" data-reference="act6"></span>
        <span class="learnosity-submit-button"></span>
<!--scripts go here-->


Initializing the API

The last thing we need to do is initialize the Items API. For this tutorial, we’ll be using Question-level events and public methods to display the distractor rationale. We’ll also use jQuery, added in line 62, to help along the way.

When the Items API is initialized, we pass to it the aforementioned signed request and an events object. This object contains a listener for the ready event fired when the Items API is ready, as well as additional event listeners, if desired. Our ready listener is defined in lines 67 through 108. When the API is ready, the loop begun in line 68 walks through every Question in the assessment. For each, the API provides a reference to the Question and its response ID.

62 <script src="js/vendor/jquery.min.js"></script>
63 <script src="//items.learnosity.com?[VERSION]"></script>
64 <script>
66   var eventOptions = {
67     readyListener: function () {
68       $.each(itemsApp.questions(), function(responseID, question) {
69         question.on("validated", function() {
70           if (question.isValid()) { return; }
72           var outputHTML = "";
74           if (question.mapValidationMetadata('distractor_rationale_response_level') != false) {
75             map = question.mapValidationMetadata('distractor_rationale_response_level');                 
76             $.each(map.incorrect, function (i, data){
77               // Each item in the `map.incorrect` array is an object that contains a `value` property that
78               // holds the response value; an `index` property that refers to the shared index of both the
79               // response area and the metadata; and a `metadata` property containing the metadata value
81               var distractor = data.metadata;
83                 outputHTML = '<li>' + distractor + '</li>';
84               });
85               if(outputHTML) {
86                 outputHTML = '<ul>' + outputHTML + '</ul>';
87               }
88             } else if (question.getMetadata().distractor_rationale) {
89               outputHTML = question.getMetadata().distractor_rationale;
90             }
92             if(!outputHTML) {
93               outputHTML = 'Have you answered all possible responses?';
94             }


Parsing Rationale When a Question is Validated

The first thing we do is bind a validated event to each Question using the on() public method in line 69. This will trigger each time a Question is validated. In the case of this tutorial, this will occur when the student clicks on the Check Answers button. When the validated event fires, we Immediately check the isValid() public method in line 70 and exit the function if the response is correct. If the response is not valid, we begin to create the outputHTML display string in line 72.

Next, in line 74, we call question.mapValidationMetadata('distractor_rationale_response_level'), which will return false if the question does not contain Distractor Rationale at Response Level.

If not false, we use it to populate the map variable with the Question’s per response distractor rationale. The mapValidationMetadata() method will create an object for all correct, incorrect, and unattempted responses. The resulting object contains the value of the response, an index that matches the distractor with its corresponding rationale, and the metadata string of that rationale. In our case, we only want the incorrect answers, so we iterate through the map.incorrect array in lines 76 to 84, wrapping each rationale in a list item. If any rationales are found, we wrap the list Item(s) in an unordered list tag in line 86.

If, in the opposite, the mapValidationMetadata() method returned false for'distractor_rationale_response_level', we know that this question does not contain Distractor Rationale at Response Level, and therefore we’ll use the getMetadata() method in line 89 to get the unique Distractor Rationale for this question.

We’ve already left the event handler in line 70 if the response is correct, and we’ve only worked through the incorrect responses in the preceding loop. Therefore, if the student only selected one correct answer from 2 or more expected, the outputHTML variable will still be empty, and we can create a prompt string in line 93 to tell the student that more answers are expected.

Displaying the Rationale

All that remains is handling the display of the rationale. Because the Items API provided us with the responseID for each question, we can use that to target or create a container for the rationale inside each response parent. Line 96 checks to see if the container has already been created. If so, line 97 populates the container and fades it in. If not, line 99 appends a div with the appropriate class and content and appends it to the response.


                if ($("#" + responseID + "_distractor").length) {
                   $("#" + responseID + "_distractor").html(outputHTML).fadeIn();
                } else {
                  $("#" + responseID).append('<div id="' + responseID +
                    '_distractor" class="distractor">' + outputHTML + '</div>');

//renderMath() Renders all Latex or MathML elements on the page with MathJax.

question.on("changed", function(responseID, question)){
$("#" + responseID + "_distractor").fadeout();

var itemsApp = LearnosityItems.init(<?php echo $signedRequest; ?>, eventOptions);

As a bonus, to account for any possible LaTeX or MathML that may have been added to the distractor rationale, line 103 uses the renderMath() function.

Finally, we bind another event to each Question in lines 105 through 107 to account for edits to student responses. Each time the student alters a response, the changed event will fire and the distractor rationale container will fade out and await a new validation.

Now that the eventOptions object is complete, we can send it, along with the signed request object, to initialize the Items API in line 113.


The Resulting Assessment

The following figures show portions of the finished report. Questions containing a single global Distractor Rationale show a simple string, and Questions containing Distractor Rationales at Response Level show a list of Item for each incorrect response. Partially correct responses prompt for additional information, and correct answers show no distractor rationale at all.


Figure 4: Single Distractor Rationale


Figure 5: Multiple Distractor Rationale per Response Level



Figure 6: Partially Correct Answer Rationale



Figure 7: Correct Answer (No Rationale)


What you learned

In this tutorial you learned two possible ways to show distractor rationale for responses. In any Question, distractor rationale can be added at the per_question and per_response level. Upon validation, you can map distractor rationale to correct, incorrect, and unattempted responses.


Was this article helpful?

Did you arrive here by accident? If so, learn more about Learnosity.