How to Collect Text Responses with xAPI and Storyline

Collect open text responses with xAPI tutorial cover photo

In this tutorial, you'll learn how to capture a user's response to an open-ended question 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.

This ability of xAPI to capture text entry data is one of its strengths, and if you were able to complete the Getting Started series successfully, then this should be fairly easy to implement.

If you want to earn recognition for your progress on this tutorial, then sign up for the xAPI Challenges App. Also, feel free to ask for help in my eLearning Slack channel (free for mailing list subscribers).

Let's get started!

Setting up your Storyline File

First, let's make sure that your Storyline file is set up appropriately. You can return to Part 2 of the Getting Started series for in-depth instructions, but, in short, you should do the following:

  1. Add a text entry field to the slide
  2. Add your question and a submit button to the slide
  3. Style the slide as desired
  4. Change the variable name from "TextEntry" to something that better describes the answer to the question

For my question, "What did you do to prepare for this exam?" I changed the variable name to userPrepResponse.

Setting userPrepResponse to typed value when text entry loses focus trigger

Once you have your Storyline slide set up and associated text response variable renamed, you're ready to move on to the next step.

Adding the Result and Response Objects

Now it's time to dive back into the code. Let's open up the xapi-statement.js file that we created during the Getting Started tutorial series. (If you've already completed the Collect Activity Info with xAPI tutorial, then you can build on the code you wrote for that).

We're going to add a "result" object to the statement. This optional "result" object can hold plenty of measurable information about the event you're recording, such as whether or not it was a success, what the current success rate is, and whether or not the event is complete.

However, the result property that we're most concerned with is the "response" object. This is where we will hold the user's response to our open-ended question. So, let's adjust our statement accordingly.

Within the sendStatement() function, add a "result" object after the "object" object. This new "result" object should take one key:value pair; the key should be "response", and, for now, just enter a placeholder for the value. The code you're adding should look like this:

{% c-block language="js" %}
"result": {
 "response": "This is a placeholder!"
}
{% c-block-end %}

In the context of the overall function, your code 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 conf = {
   "endpoint": "https://your-lrs-endpoint.com/",
   "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": {
      "response": "This is a placeholder!"
    }
 };
 const result = ADL.XAPIWrapper.sendStatement(statement);
}
{% c-block-end %}

Important note: Don't forget to add a comma after the closing bracket of the "object" object...right before you define the "result" object.

Now that the structure of our "result" object is in place, we need to deal with the placeholder. We'll do that next!

Passing the Storyline Variable to the xAPI Statement

As it stands, our user's response gets stored in a Storyline variable once their focus leaves the text entry box. We need to pull that variable into our xAPI statement and place its contents where our placeholder is.

If you remember doing this with the user's name and email in Part 2 of the Getting Started series, then you're on the right track!

Let's create a new JavaScript variable and set its contents to our Storyline variable:

{% c-block language="js" %}
const userResponse = player.GetVar("userPrepResponse");
{% c-block-end %}

Naturally, you would replace userPrepResponse with whatever you decided to name your variable in Storyline.

With this new JavaScript variable in place, we can replace our placeholder with it, like so:

{% c-block language="js" %}
"result": {
 "response": userResponse
}
{% c-block-end %}

However, we have a slight issue. What if there's another open text response question on the following slide and we want to re-use our sendStatement() function? As the moment, that would not be possible.

If you're thinking of parameters, then you're exactly right. Just as we pass in the verb, verbId, object, and objectId to our function as arguments, we can pass in our Storyline variable's name as an argument.

First, let's add a parameter to our function that can account for the Storyline variable:

{% c-block language="js" %}
function sendStatement(verb, verbId, object, objectId, openTextVar) {
 ...
}
{% c-block-end %}

With this in place, we can replace our Storyline variable's name with the variable that we just added as a parameter:

{% c-block language="js" %}
const userResponse = player.GetVar(openTextVar);
{% c-block-end %}

The "result" object can stay the same since the userResponse JavaScript variable now grabs whatever Storyline variable we tell it to.

Our final sendStatement() function should look like this:

{% c-block language="js" %}
function sendStatement(verb, verbId, object, objectId, openTextVar) {
 const player = GetPlayer();
 const uNamejs = player.GetVar("uName");
 const uEmailjs = player.GetVar("uEmail");
 const userResponse = player.GetVar(openTextVar);
 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": {
     "response": userResponse
   }
 };
 const result = ADL.XAPIWrapper.sendStatement(statement);
};
{% c-block-end %}

We can now call this function using a Storyline "Execute JavaScript" trigger whenever the user clicks the Submit button. We pass our arguments into the function, like so:

{% c-block language="js" %}
sendStatement("answered", "http://adlnet.gov/expapi/verbs/answered", "Exam Prep Question", "https://www.devlinpeck.com/exam-prep-question", "userPrepResponse");
{% c-block-end %}

send_statement function with code enclosed

If you've completed the Collect Activity Info with xAPI tutorial, then your code will look slightly different:

{% c-block language="js" %}
function sendStatement(verb, verbId, object, objectId, objectDescription, activityType, openTextVar) {
 const player = GetPlayer();
 const uNamejs = player.GetVar("uName");
 const uEmailjs = player.GetVar("uEmail");
 const userResponse = player.GetVar(openTextVar);
 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 },
       "description": { "en-US": objectDescription },
       "type": activityType
     },
     "objectType": "Activity"
   },
   "result": {
     "response": userResponse
   }
 };
 const result = ADL.XAPIWrapper.sendStatement(statement);
};
{% c-block-end %}

If you're building from the previous tutorial, you will also need to pass all seven arguments to your function when you call it with your Execute JavaScript trigger in Storyline.

Publishing Your Course

And there we have it! After you publish the course and modify the output folder accordingly, the slide will send xAPI statements with the user's response whenever they click the submit button. 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

Once you're able to use xAPI to capture user's open text responses as we've done in this tutorial, you are able to collect valuable information in a way that's very user-friendly for your learners.

Without this method, you may rely on survey tools or bringing users out of the eLearning experience into a survey hosted on the LMS.

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