How to Collect Quiz Score Info with xAPI and Storyline

Collect Quiz Scores with xAPI tutorial cover photo

In this tutorial, you'll learn how to send score information about a quiz or assessment that a user takes in your Storyline course using xAPI.

Before continuing, ensure that you’ve completed the 3-part Getting Started with xAPI Tutorial Series. We will build upon what you’ve learned in those tutorials, so it’s important that you’re familiar with the basics.

So, let's say that we have a 10-question quiz at the end of an eLearning course that we developed with Articulate Storyline. We want to know whether or not the user achieved the 80% passing score to receive credit for the course.

Using xAPI, we can send a statement at the end of the quiz telling us exactly what the user's score was and whether or not they passed.

And, while SCORM is fully capable of collecting this type of information, xAPI opens up additional capabilities.

For example, imagine that we have several quizzes in one Storyline course. Each of these can report their score data to the LRS separately, whereas with SCORM, you can only report one set of results per course package.

Adding the ability to track score information to your xAPI toolkit is also great because it offers you deeper insights when it comes time to query your data.

For instance, you may want to know whether people who accessed a specific learning resource are performing better on the quiz. Having this quiz score data right there in your LRS makes it much easier to answer these questions.

Let's get started!

Modifying the xAPI Statement

We'll start things off by modifying the xAPI statement code within your sendStatement() function. Open your xapi-statement.js file in your text editor of choice.

Important note: If you've completed additional xAPI tutorials beyong the Getting Started series, then your code will  have additional elements. We will work from the base version linked above. It's up to you whether you add to the file you've been working on or follow along by working from the base version.

Adding the Result Object

First, let's add a "result" JSON object right after the "object" JSON object, like so:

{% c-block language="js" %}
const statement = {
 "actor": {
   "name": uNamejs,
   "mbox": "mailto:" + uEmailjs
 },
 "verb": {
   "id": verbId,
   "display": { "en-US": verb }
 },
  "object": {
   "id": objectId,
    "definition": {
     "name": { "en-US": object }
    }
  },
 "result": {

 }
};
{% c-block-end %}

Important note: Don't forget to add a comma after the closing } of the "object" JSON object.

You may be familiar with the result object from the Collect Open Text Responses with xAPI tutorial, but if this is your first time seeing it, then all you need to know is that this object holds important "result" information about your statement.

If you'd like to learn about all the elements that the result object can hold, then you can read this deep dive resource.

Adding the Score Object

For now, we're concerned with the score element, which is a property of the "result" object and also an object of its own. Let's add this object now:

{% c-block language="js" %}
"result": {
 "score": {

 }
}
{% c-block-end %}

The "score" object holds four optional properties, which are as follows:

  • Min: The minimum possible score, which in most cases will be 0.
  • Max: The maximum possible score, expressed as an integer.
  • Raw: The user's raw score, expressed as an integer.
  • Scaled: The user's scaled score, expressed as a decimal between 0 and 1.

Let's add each property and assign 0 to "min," since we know that the minimum score will be 0:

{% c-block language="js" %}
"result": {
 "score": {
   "min": 0,
   "max": ,
   "raw": ,
   "scaled":
 }
}
{% c-block-end %}

We know our min value, but how do we pull in the maximum possible score and the user's raw score at the end of the quiz?

If you've been following along with the other tutorials, then you probably know the answer: we'll pull these values from Storyline variables, just as we did with the user's name and email address in the 2nd tutorial of the Getting Started series.

For example, in a later section, we will create a Storyline variable named "userScore" that holds the user's score on the Storyline quiz. We will also create a Storyline variable named "maxScore" that holds the user's maximum score. Let's assign these Storyline variables to JavaScript variables like so:

{% c-block language="js" %}
const userScorejs = player.GetVar("userScore");
const maxScorejs = player.GetVar("maxScore");
{% c-block-end %}

Let's add this code to the xapi-statement.js file where we've defined the rest of the variables. (Remember...we will create the corresponding Storyline variables in a later section).

Now we can use the userScorejs variable to represent the user's score and the maxScorejs variable to represent the maximum score. And, since the user's scaled score should be a fraction (the number of correct responses out of the total number of questions), we can create another variable to represent the scaled score:

{% c-block language="js" %}
const scaledScore = userScorejs / maxScorejs;
{% c-block-end %}

With these variables in place, we can populate the rest of our Result object:

{% c-block language="js" %}
"result": {
 "score": {
   "min": 0,
   "max": maxScorejs,
   "raw": userScorejs,
   "scaled": scaledScore
 }
}
{% c-block-end %}

We're in good shape so far, but what if we want to know whether or not the user passed the quiz? Of course we can look at their scaled score and check whether or not it's above 0.8 (assuming we are requiring they score 80% or above to pass), but we can make it even easier than that.

Let's see how...

Adding the Success Property

The "result" object can also hold an optional "success" property. This property's value should be either true or false (true if the event was successful...in this case, if the user passed the quiz, or false if the event was not successful).

Let's create the success property now, leaving it blank until we create a corresponding variable that can hold the true or false value:

{% c-block language="js" %}
"result": {
 "score": {
   "min": 0,
   "max": maxScorejs,
   "raw": userScorejs,
   "scaled": scaledScore
 },
 "success":
}
{% c-block-end %}

Important note: Don't forget your comma after the "score" object's closing curly bracket.

Creating a conditional JavaScript variable

Now it's time to create the variable that will tell us whether or not the user passed the quiz.

Let's create a JavaScript variable titled "userDidPass" that will resolve to true if scaledScore is greater than or equal to 0.8, or false if scaledScore is less than 0.8.

We can write this code using JavaScript's conditional operator, like so:

{% c-block language="js" %}
const userDidPass = scaledScore >= 0.8 ? true : false;
{% c-block-end %}

This will likely look confusing if you're not familiar with JavaScript conditional (ternary) operators. Here's how it works:

{% c-block language="js" %}
(some expression goes here) ? (if the expression resolves to true, return this) : (if the expression resolves to false, return this)
{% c-block-end %}

You can check out the link above if you're interested in learning more about ternary operators, but this modern use of JavaScript replaces this equivalent chunk of code:

{% c-block language="js" %}
if (scaledScore >= 0.8) {
 userDidPass = true;
} else {
 userDidPass = false;
}
{% c-block-end %}

Anyway, let's move on! With our new userDidPass variable resolving to true or false depending on whether the user passed the quiz, we can insert that variable as the "success" property's value:

{% c-block language="js" %}
"result": {
 "score": {
   "min": 0,
   "max": maxScorejs,
   "raw": userScorejs,
   "scaled": scaledScore
 },
 "success": userDidPass
}
{% c-block-end %}

The Final Code

If you've followed along successfully, then your final sendStatement() function should look like this:

{% c-block language="js" %}
function sendStatement(verb, verbId, object, objectId) {
 const player = GetPlayer();
 const uNamejs = player.GetVar("uName");
 const uEmailjs = player.GetVar("uEmail");
 const userScorejs = player.GetVar("userScore");
 const maxScorejs = player.GetVar("maxScore");
 const scaledScore = userScorejs / maxScorejs;
 const userDidPass = scaledScore >= 0.8 ? true : false;
 const conf = {
   "endpoint": "https://trial-lrs.yetanalytics.io/xapi/",
   "auth": "Basic " + toBase64("1212:3434")
 };
 ADL.XAPIWrapper.changeConfig(conf);
  const statement = {
    "actor": {
     "name": uNamejs,
     "mbox": "mailto:" + uEmailjs
   },
   "verb": {
     "id": verbId,
     "display": { "en-US": verb }
   },
   "object": {
     "id": objectId,
     "definition": {
       "name": { "en-US": object }
     }
   },
    "result": {
     "score": {
       "min": 0,
       "max": maxScorejs,
       "raw": userScorejs,
       "scaled": scaledScore
      },
     "success": userDidPass
    }
  };
 const result = ADL.XAPIWrapper.sendStatement(statement);
}
{% c-block-end %}

Important note: Your API key and secret will look different, but we will have a refresher on setting the conf object later in the tutorial.

Setting up the Storyline File

First, let's create two variables in our Storyline course:

  1. maxScore: This variable will hold the user's maximum score on the quiz. Set this variable to however many questions you include in the quiz. Thanks to the JavaScript variable that we set up earlier, you will only need to change the Storyline variable whenever the number of questions changes.
  2. userScore: This variable will need to increment by one whenever the user answers a question correctly. It will hold the number of questions that the user has answered correctly.
maxScore and userScore variables in Storyline variable manager

Now we need to make sure that we increment our "userScore" variable by one whenever the user selects a correct choice.

The easiest way to do this would be with an "Adjust variable" trigger that adds to the variable whenever the user selects the correct response. You can see an example of this in the screenshot below.

Storyline slide sample showing trigger that adds 1 to userScore variable

If you need additional help with variables, you can view this official Articulate documentation.

Sending the xAPI Statement

Once the user reaches the results slide, we need to send our xAPI statement to the LRS. Since we already did all of the hard work setting up the JavaScript function, sending our statement is as easy as passing in the desired arguments.

Also note that this will look exactly as it did in the previous tutorials. Since we did not add any new parameters to our function for the score reporting functionality to work, we only need to pass in the verb, verbId, object, and objectId (and any other arguments that you've added in previous tutorials).

Important note: If any of this is confusing, now may be a good time to review Part 3 of the Getting Started series.

I will use the following JavaScript in an "Execute JavaScript" trigger to send an xAPI statement from my example quiz's results slide:

{% c-block language="js" %}
sendStatement("completed", "http://activitystrea.ms/schema/1.0/complete", "Sky Quiz", "https://www.devlinpeck.com/quizzes/sky-quiz");
{% c-block-end %}

You can use whichever verb and object suit your project. The best place to execute this trigger would be when the timeline starts on your results slide.

Publishing Your Course

Once you have that trigger in place, you're ready to publish your course! After you publish the course and modify the output folder accordingly, the course will send quiz result data upon reaching the results slide. You will need to add the xapi-statements.js and xapiwrapper.min.js files every time you publish your Storyline course.

You will also need to ensure that you're collecting the user's name and email address from the course if you want the code to work as shown.

If you'd rather use a filler name and email address to test your code, you can replace those variables with strings, like so:

{% c-block language="js" %}
const uNamejs = "testname";
const uEmailjs = "testemail@test.com";
{% c-block-end %}

Finally, if you haven't configured your "conf" object to work with your LRS, then your statements will not send successfully. See here if you need additional help setting the "conf" object.

Conclusion

Great work! Now you can collect rich score information from your Storyline quizzes and assessments using xAPI.

If you had any issues sending your statements, then you should refer to the common xAPI and Storyline troubleshooting steps.

Return to the Full Guide to xAPI and Storyline.

Featured Tutorials

View all tutorials