Create an xAPI-Powered Leaderboard in Storyline

Create an xAPI Leaderboard tutorial cover photo

In this final tutorial of the Full Guide to Storyline and xAPI, you'll learn how to leverage xAPI data to create a fun, motivating leaderboard as part of an Articulate Storyline course.

Leaderboards have the potential to inspire friendly competition and show people how they stack up against their peers, so learning how to do this is a great way to bring value to your learning design team or practice.

Also, since this is the last tutorial in the series and we will draw on many concepts from previous tutorials, I highly recommend that you complete the other tutorials before continuing. Particularly, you should complete the Getting Started with xAPI Tutorial Series and the Query the LRS tutorial.

If you've followed along with those tutorials successfully, then you are ready to dive into this one.

Our goal: Query data from one of my sample LRSs to populate a leaderboard in Storyline that shows the top 5 quiz scores, as well as the users' names who earned the scores.

Creating the Leaderboard in Storyline

First, let's set up our Storyline file. We need 10 custom variables to hold the top 5 users and the top 5 scores. So, create 10 new text variables in a new Storyline project with the following names:

  • firstUser (this will hold the first place user's name)
  • firstScore (this will hold the first place user's score)
  • secondUser
  • secondScore
  • thirdUser
  • thirdScore
  • fourthUser
  • fourthScore
  • fifthUser
  • fifthScore

Your variable manager should look like this:

Storyline variable manager for xAPI leaderboard

Now that we've created the variables, let's create the leaderboard itself. I recommend placing a title on the slide, a brief description of the leaderboard, and a 2x6 chart to hold the names and scores.

Within the chart (or however you decide to lay out the leaderboard), use variable references to display the variables' values.

You can add variable references by putting a percent sign before and after your variable's name. For example: %firstUser%.

Once you're done laying out your slide and adding the variable references, your leaderboard slide should look something like this:

xAPI Quiz Leaderboard with Storyline variable placeholders

There's one final thing we need to do in the Storyline file. We need to create an "Execute JavaScript" trigger to execute the function that we're going to create in the next section. This function will change the Storyline variables so that they hold the desired values.

So, let's create an "Execute JavaScript" function that fires when the timeline starts on the leaderboard slide. We want it to execute the following JavaScript:

{% c-block language="js" %}
populateLeaderboard();
{% c-block-end %}

See this screenshot for further clarification:

With this trigger in place, the variables created, and the variable references on the slide, we're finished with Storyline.

Feel free to publish your project and add the xapiwrapper.min.js file now, or you can wait until we get to the end when our JavaScript file is ready, too.

Building and Sending the Query

Now it's time to dive into the JavaScript. We need to build the populateLeaderboard() function that will query the LRS and adjust the Storyline variables with the top 5 users and scores.

Let's create a new JavaScript file and save it as xapi-query.js (Important note: we'll need to add this file to our published course output later in the tutorial).  

Before creating the populateLeaderboard function, let's set the "conf" object and authenticate ourselves with the LRS. We can do so using the following code:

{% c-block language="js" %}
const conf = {
 "endpoint": "https://devlins-test-lrs.lrs.io/xapi/",
 "auth": "Basic " + toBase64("user:password")
};
ADL.XAPIWrapper.changeConfig(conf);
{% c-block-end %}

I recommend leaving this code exactly as it is for the purposes of this tutorial. It connects us to an LRS that I've pre-populated with data for us to use.

Next, we'll create the populateLeaderboard() function and send a query to our LRS. Let's define the parameters so that the query returns all statements where users have passed the xAPI Sample Quiz:

{% c-block language="js" %}
function populateLeaderboard() {

 const parameters = ADL.XAPIWrapper.searchParams();
 parameters["verb"] = "http://adlnet.gov/expapi/verbs/passed";
 parameters["activity"] = "http://example.com/xapi-sample-quiz";
 const queryData = ADL.XAPIWrapper.getStatements(parameters);

}
{% c-block-end %}

Important note: See the How to Query the LRS tutorial if this code confuses you.

Now we can access all of our returned statements with dot notation by typing "queryData.statements." This gives us access to an array of our statement objects, which is essentially a list of the full xAPI statements that we've returned.Next, we need to:

  1. Create a variable, "statements" to easily refer to queryData.statements, and
  2. Sort our statements array so that the object with the highest score is first in the array, the object with the second highest score is second in the array, and so on and so forth.

The code that we can use to accomplish this is as follows:

{% c-block language="js" %}
function populateLeaderboard() {

 const parameters = ADL.XAPIWrapper.searchParams();
 parameters["verb"] = "http://adlnet.gov/expapi/verbs/passed";
 parameters["activity"] = "http://example.com/xapi-sample-quiz";
 const queryData = ADL.XAPIWrapper.getStatements(parameters);

 let statements = queryData.statements;
 statements.sort(function(a, b) {
   return b.result.score.scaled-a.result.score.scaled;
 })

}
{% c-block-end %}

You're likely comfortable by now with how we created the "statements" variable, but what about the next line of code?

We used the array sort method to sort an array of objects by one of their properties. In this case, the property that we're sorting by is the scaled score that the user earned on the quiz.

The syntax may look slightly confusing if you're not familiar with the sort method, but the important thing to know is that this arranges our array of objects so that the object with the highest score is the first in the array, and the array of objects is sorted in descending order.

Now that our statements are sorted as desired, we're ready to continue. Review the links in this section if you'd like to learn more about how the above code works.

Important note: If you're going to reuse this code on your own projects, then you may need to modify it depending on how your xAPI data is structured and stored in your LRS. The main thing to remember is that you can access your xAPI properties using dot notation, as discussed in the Query the LRS tutorial. You can also use JavaScript skills to sort and arrange the data as needed.

From here, we'll use a for loop to convert the scaled scores into values that we want to display on the leaderboard.

Specifically, we need to multiply the scaled scores by 100, since they're only decimals at the moment, and we need to add percentage signs:

{% c-block language="js" %}
function populateLeaderboard() {

 const parameters = ADL.XAPIWrapper.searchParams();
 parameters["verb"] = "http://adlnet.gov/expapi/verbs/passed";
 parameters["activity"] = "http://example.com/xapi-sample-quiz";
 const queryData = ADL.XAPIWrapper.getStatements(parameters);

 let statements = queryData.statements;
 statements.sort(function(a, b) {
   return b.result.score.scaled-a.result.score.scaled;
 })

 for (let i = 0; i < statements.length; i++) {
   statements[i].result.score.scaled = statements[i].result.score.scaled * 100 + "%";
 }

}
{% c-block-end %}

The for loop above cycles through each statement that we returned, multiplies the scaled score by 100, and adds a percentage sign after the score. The value is stored in the same place on the object that it was stored before.

Finally, we need to update the Storyline variables with the values from our sorted array of statements.

To do this, let's define the "player" variable. After that, we'll use Storyline's SetVar method to change our Storyline variables' values based on the sorted statement data.

We can use dot notation to access the different elements of our statement. For example:

{% c-block language="js" %}
statements[0].result.score.scaled /* This returns the scaled score from the first object in the array */
statements[0].actor.name /* This returns the user's name from the first object in the array */
{% c-block-end %}

Our final xapi-query.js file will look like this:

{% c-block language="js" %}
const conf = {
 "endpoint": "https://devlins-test-lrs.lrs.io/xapi/",
 "auth": "Basic " + toBase64("user:password")
};
ADL.XAPIWrapper.changeConfig(conf);

function populateLeaderboard() {

 const parameters = ADL.XAPIWrapper.searchParams();
 parameters["verb"] = "http://adlnet.gov/expapi/verbs/passed";
 parameters["activity"] = "http://example.com/xapi-sample-quiz";
 const queryData = ADL.XAPIWrapper.getStatements(parameters);

 let statements = queryData.statements;
 statements.sort(function(a, b) {
   return b.result.score.scaled-a.result.score.scaled;
 })

 for (let i = 0; i < statements.length; i++) {
   statements[i].result.score.scaled = statements[i].result.score.scaled * 100 + "%";
 }

 const player = GetPlayer();

 player.SetVar("firstUser", statements[0].actor.name);
 player.SetVar("secondUser", statements[1].actor.name);
 player.SetVar("thirdUser", statements[2].actor.name);
 player.SetVar("fourthUser", statements[3].actor.name);
 player.SetVar("fifthUser", statements[4].actor.name);

 player.SetVar("firstScore", statements[0].result.score.scaled);
 player.SetVar("secondScore", statements[1].result.score.scaled);
 player.SetVar("thirdScore", statements[2].result.score.scaled);
 player.SetVar("fourthScore", statements[3].result.score.scaled);
 player.SetVar("fifthScore", statements[4].result.score.scaled);

}
{% c-block-end %}

Voila! Save your file once the code matches the code above.

Publishing Your Course

If you didn't publish your Storyline project in the first section of this course, then you should do so now. Follow these instructions to publish the HTML5 package and add the xapiwrapper.min.js file to the published folder. You don't need to add an xapi-statement.js file to this folder, but you do need to add the xapi-query.js file that we've just created.

File structure for published Storyline output using xAPI leaderboard

Next, open the story.html file (or story_html5.html file if you have an older version of Storyline) with your code editor of choice. Find the closing style tag, which will look like this:

{% c-block language="markdown" %}
</style>
{% c-block-end %}

And add the following two lines of code right after it to link your Storyline project to your xapiwrapper.min.js and xapi-query.js files, respectively:

{% c-block language="markdown" %}
<script type="text/javascript" src="xapiwrapper.min.js"></script>
<script type="text/javascript" src="xapi-query.js"></script>
{% c-block-end %}

With this code in place, save your file. Now you can open the story.html (or story_html5.html) file and the leaderboard will populate with the top 5 names and quiz scores.

Final xAPI-enabled leaderboard in Storyline with names and scores

Congraulations! Now that you know how to query an LRS and display results in a Storyline course, you can alter the code we used here and adapt as needed for your own projects.

If you don't see the names or quiz scores, then double check your code and read over the common xAPI and Storyline troubleshooting steps.

Limitations to this Approach

As mentioned in the Query the LRS tutorial, this approach will not work if you need to query thousands of statements. It is best suited for queries that will return up to 50 statements.

Therefore, if hundreds or thousands of users have passed the quiz, then it would not make sense for us to query the statements in the way that we did in this tutorial.

Furthermore, this approach requires "Read" permissions from your LRS to access all of the statements. This means that any technically savvy users who have access to your Storyline course would be able to query your LRS in the same way that we did here, thereby exposing all of the data within.

An alternate approach would be to create a leaderboard dashboard in your LRS and embed it in your Storyline course.

Finally, ensure that you're compliant with data and privacy regulations and requirements before implementing this on real projects.

Next Steps

If you've been with me since the first tutorial in the series, then here's a HUGE THANKS for following along! This series was over a year in the making, and I hope that you've learned as much following along with the tutorials as I have learned writing them.

Going forward, I urge you to leverage xAPI in any projects where you may be able to use the data to improve your learning design.

If you want to learn more, I highly recommend that you explore learning analytics and data-driven decision making. This tutorial series taught you how to implement xAPI in a Storyline course and bring that data back into the course, but you must analyze xAPI data and draw actionable conclusions to make the most of it.

As always, I'm here for you if you need me. I love working on xAPI implementation projects, so feel free to contact me if you need a hand.

Finally, please share the Full Guide to xAPI and Storyline on social media. The more people that we get this guide in front of, the more valuable data that we can collect about our digital learning experiences.

Thank you again and good luck on your future xAPI endeavors!

Featured Tutorials

View all tutorials