Last Friday, we were excited to be involved in xAPI Party. The celebration marked the end of the xAPI Cohort run by Torrance Learning (their next Cohort starts February 1) and our Director of Products TJ Seabrooks gave a demo. Since many of the folks at the xAPI Cohort were familiar with the free LRS in SCORM Cloud, we wanted to share a story of how we helped one of our clients with a super particular xAPI problem, so TJ shared how to add attachments to an xAPI course in Lectora.

This specific example comes from a client who is large and in a highly regulated industry. Before working with us, their certificates were tied to the end of each course. If a learner were to lose the certificate, they’d have to go back, relaunch the course and redownload the certificate. They needed the ability to maintain and later present certificates for learners so that any administrator in the organization could go into the LRS, view the learners’ scores and download certificates for each learner.

Our solution was to build out a simple reporting system that let them view xAPI attachments as part of a grade book reporting system. xAPI is particularly well-suited to this solution because it is reusable: any content you author can be launched from any LMS that supports xAPI and any LRS can store and fetch the attachments. This is unlike SCORM, with which you would need to build a custom solution that only works for a single system.

Since our customer was already familiar with Lectora, we provided instructions for setting an action in a Lectora course that lets us send our own custom event when we’re finished with the course. If you don’t use Lectora, you could adapt these steps to another authoring tool that supports xAPI pretty easily (and if you’re struggling – just reach out to us).

If you want to see everything “in action,” you can check out a video of TJ’s demonstration, head over to Torrance Learning’s Adobe Connect recording. In the video demo, TJ walks you through how to add the custom JavaScript code (TinCanJS and html2canvas) to Lectora, test the course in SCORM Cloud and then retrieve the certification in our custom reporting dashboard, which shares info like score, pass/fail, launch date and assessment result.

Technical steps for sending and receiving attachments in Lectora

To include TinCanJS in the project, you need to create an HTML Extension object (Figure 1) and set the “Type” property to “Top of file scripting” (Figure 2).

Figure 1

Figure 2

Click edit (seen in Figure 2 above) and add the TinCanJS file like you would on a webpage (through a <script> tag with a relative URL). Or paste the code in between <script> tags (Figure 3).

Figure 3

Something to note: any code in an HTML Extension has to be valid HTML, so if any JS added isn’t inside a <script> tag, it will break.

Next, to create the screenshot of the page, use html2canvas. You’ll need to add the html2canvas source code the same way you did for TinCanJS. If you want to capture and send the current document, i.e. the page where you loaded the JS, pass “document.body” to the html2canvas() function. This function returns a promise, so use a .then() to process the screenshot. The parameter passed to the .then() is an HTML canvas object, so to get the content and the content-type, we have to use the canvas.toDataURL() function. The format for a DataURL is as follows:

data:[<contenttype>][;base64],<content>

Figure 4

You will need to parse this string to get the content-type and the content itself (code for parsing shown in Figure 4). The content will be the raw binary data, which is what should be placed in the content section of the attachment.

In the code shown in Figure 4, cfg is the object that contains all of the other parameters of the statement. Cfg.attachments is an array that holds all of the attachments associated with that statement. Note that the code shown in Figure 4 happens after you have set up both the LRS and the other parameters of the statement in your Javascript.
Once you send the statement, you can run queryStatements() on the LRS, which will return a StatementsResult object. When you run queryStatements(), be sure to set the “attachments” flag to “true” (shown in Figure 5). This flag lets the LRS know to return both the statements and any attachments that it has.

Figure 5

Once the StatementsResult object is returned, you can iterate through its list of statements until you find the statement that has the attachment. To download the file, you’ll need to run the following code:

var link = document.createElement(“a”);
link.download = “Test.png”;
link.href = “data:”+<attachment>.contentType+”;base64,”+<attachment>.content;
link.click();

Where <attachment> is the TinCan.Attachment object whose content you want to download. This code reconstructs the dataURL, and then uses that to download the file. One thing to note is that the file extension of “link.download” should match the filetype of the attachment (if the file is a .jpeg, it should be .jpeg, if it’s a .pdf, it should be .pdf, etc.).

Customize for your needs

This is a basic guide for creating, sending and receiving attachments inside Lectora. It can be completely customized to your needs. For example, the object passed to html2canvas() can be any HTML element, not just the entire document. Therefore, if you only wanted to print a certain <div> on the page, you can pass that element instead.

Also, the TinCanJS library is designed to make sending and receiving attachments easier for the user, so the queryStatements() function handles verifying that the right attachment content is put with the rest of its data and attached to the correct statement.

If you have any questions implementing these steps or are curious about how we can help you with xAPI in general, reach out to us. In the meantime, we’d recommend checking out the free LRS in SCORM Cloud and the xAPI Open Source libraries.

Kirsty leads the charge on general marketing items at Rustici Software as our Marketing Manager. A filmmaker by schooling, she’s taken up the writer’s call to arms, leading our efforts on content, content, content.