{"id":73,"date":"2015-12-06T18:42:53","date_gmt":"2015-12-06T23:42:53","guid":{"rendered":"https:\/\/willchinda.com\/blog\/?p=73"},"modified":"2015-12-06T18:53:26","modified_gmt":"2015-12-06T23:53:26","slug":"xapi-captivate-part-2","status":"publish","type":"post","link":"https:\/\/willchinda.com\/blog\/2015\/12\/06\/xapi-captivate-part-2\/","title":{"rendered":"xAPI + Captivate (part 2) &#8211; Results and Leaderboards"},"content":{"rendered":"<p>So in <a href=\"https:\/\/willchinda.com\/blog\/2015\/10\/18\/xapi-captivate-part-1\/\">my first post<\/a> on the topic of integrating the Experience API and Captivate, I showed you how to take a couple user inputs and translate that into a simple statement that would be sent to an LRS when the user clicks a button.<\/p>\n<p>Sending a statement from a Captivate project is cool, but it&#8217;s only half the usefulness that xAPI enables. The ability to retrieve statements and report on it\u00a0without an LMS is where you really get in touch with the power of what this standard can do. In this tutorial, I&#8217;ll be\u00a0creating a slightly more complex set of statements,\u00a0searching for those\u00a0statements, manipulating them, and displaying them within Captivate.<\/p>\n<p>For this exercise, I want\u00a0the user to input a number that will serve as their\u00a0&#8220;score.&#8221; I want the statement to say\u00a0they completed the activity and I want that statement to include the score. Then I want the Captivate project to grab all other statements generated from that and display\u00a0the top 10 scores.<\/p>\n<p>And just for funsies, I&#8217;ll be publishing it as an HTML5 project instead of the SWF output we had last time. This will actually give us a handy advantage when it comes to creating the leaderboard, but\u00a0in all other respects it&#8217;s pretty similar.<\/p>\n<p>So <a href=\"https:\/\/willchinda.com\/blog\/2015\/10\/18\/xapi-captivate-part-1\/\">just like in Part 1<\/a>, I&#8217;m going to start with a Captivate file that takes in the user&#8217;s name and email address. Just like before, I&#8217;m going to change the assigned variables (using the <strong>Properties<\/strong> pane) to the more descriptive names <em>user_name<\/em> and <em>user_email<\/em>. On success, the action is set to <strong>Go to next slide<\/strong>.<\/p>\n<p><a href=\"https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/10\/userinput.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-44\" src=\"https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/10\/userinput.jpg\" alt=\"userinput\" width=\"436\" height=\"170\" srcset=\"https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/10\/userinput.jpg 436w, https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/10\/userinput-300x117.jpg 300w\" sizes=\"auto, (max-width: 436px) 100vw, 436px\" \/><\/a><\/p>\n<p><a href=\"https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/10\/textentryvariable.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-45\" src=\"https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/10\/textentryvariable.jpg\" alt=\"textentryvariable\" width=\"300\" height=\"57\" \/><\/a><\/p>\n<p>For the second slide, I&#8217;m going to add another text entry box so the user can enter a number. Just like before, I&#8217;ll change the assigned variable to the more descriptive name <em>user_score<\/em>.<\/p>\n<p><a href=\"https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/11\/enteranumber.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-76\" src=\"https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/11\/enteranumber.jpg\" alt=\"enteranumber\" width=\"216\" height=\"134\" \/><\/a><\/p>\n<p><em>[I realize this is kind of a weird activity, but it&#8217;s just designed to simulate playing a game or doing some scored activity in Captivate and passing along to the LRS as a score. In your own design, you&#8217;ll just save that score to a variable and pass it along without this user input aspect.]<\/em><\/p>\n<p>For the associated action, select the text entry box and change the <strong>On Success<\/strong> action to <strong>Execute JavaScript<\/strong>. Click the <strong>Script_Window<\/strong> button and add the following code to the popup window.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">var userName = window.cpAPIInterface.getVariableValue(\"user_name\");\r\nvar userEmail = 'mailto:' + window.cpAPIInterface.getVariableValue(\"user_email\");\r\n\r\nstmt.actor = new ADL.XAPIStatement.Agent(userEmail, userName);\r\n\r\nvar userScore = window.cpAPIInterface.getVariableValue(\"user_score\");\r\n\r\nstmt.result = { \r\n 'score': {\r\n   'raw': userScore\r\n },\r\n 'completion': true\r\n};\r\n\r\nvar resp_obj = ADL.XAPIWrapper.sendStatement(stmt);<\/pre>\n<p>So let&#8217;s unpack this a little. You can read the explanation for the first half of this\u00a0in <a href=\"https:\/\/willchinda.com\/blog\/2015\/10\/18\/xapi-captivate-part-1\/\">Part 1<\/a>\u00a0&#8211; I&#8217;m just saving the userName and userEmail from Captivate&#8217;s interface and adding them to the <em>actor<\/em> portion of our <em>stmt<\/em> (which is defined in our external js file).\u00a0I&#8217;m doing the same thing with the score entered in the second slide, adding it to <em>userScore<\/em>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">var userScore = window.cpAPIInterface.getVariableValue(\"user_score\");<\/pre>\n<p>The new bit here is <em>result<\/em>. This changes up the design of our xAPI statement a bit, which in Part 1 was just limited to the basics &#8211; <em>actor<\/em>, <em>verb<\/em>, <em>object<\/em>. Result is a new property\u00a0that we can add to our statement to give it more meaning.\u00a0There&#8217;s a bit more complexity to it, though, since the result property has several sub-properties like <em>score<\/em>, <em>completion<\/em>, and <em>duration<\/em>. Adding even more complexity, score has additional sub-properties like <em>scaled<\/em>, <em>raw<\/em>, <em>min<\/em>, and <em>max.<\/em><\/p>\n<p>Just like layers of an onion, an xAPI statement can be as superficial or as deep as you want it to go. As long as you&#8217;ve got actor, verb, and object, all of the rest is optional (for the most part), so I pick and choose based on what kind of performance or experience I&#8217;m describing in the statement.<\/p>\n<p>Confused? It may help to visualize all the available properties in this diagram Jason Haag shared on Twitter.<\/p>\n<blockquote class=\"twitter-tweet\" lang=\"en\">\n<p dir=\"ltr\" lang=\"en\">Diagram of <a href=\"https:\/\/twitter.com\/hashtag\/xAPI?src=hash\">#xAPI<\/a> Statement Objects &amp; Properties. Send feedback. Is it helpful? What&#8217;s missing? <a href=\"https:\/\/t.co\/k5H1eO8PCd\">https:\/\/t.co\/k5H1eO8PCd<\/a> <a href=\"https:\/\/twitter.com\/hashtag\/learninganalytics?src=hash\">#learninganalytics<\/a><\/p>\n<p>\u2014 Jason Haag (@mobilejson) <a href=\"https:\/\/twitter.com\/mobilejson\/status\/656895343204966400\">October 21, 2015<\/a><\/p><\/blockquote>\n<p><script src=\"\/\/platform.twitter.com\/widgets.js\" async=\"\" charset=\"utf-8\"><\/script><\/p>\n<p>If you really want to get into the ins and outs of the <em>result<\/em> property, I highly recommend <a href=\"https:\/\/tincanapi.com\/deep-dive-result\/\">this article by Brian Miller<\/a>.<\/p>\n<p>Getting back to our code, I&#8217;m adding <em>userScore<\/em> to the <em>raw<\/em> sub-property of <em>score<\/em>, since it&#8217;s&#8230; well&#8230; a raw score. I&#8217;m also setting the <em>completion<\/em> property to <em>true<\/em>, since it does in fact complete this activity.<\/p>\n<p>Our helpful xAPI Wrapper helper library only goes so far in simplifying the creation of our statement, so if we want to really dig in to certain properties, we&#8217;ve got to actually use the correct JSON format to define this part of our statement. It&#8217;s simple enough, as long as you use the <a href=\"https:\/\/github.com\/adlnet\/xAPI-Spec\/blob\/master\/xAPI.md\">xAPI specification<\/a> as your guide.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">stmt.result = { \r\n 'score': {\r\n   'raw': userScore\r\n },\r\n 'completion': true\r\n};<\/pre>\n<p>The final line of code just sends our statement.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">var resp_obj = ADL.XAPIWrapper.sendStatement(stmt);<\/pre>\n<p>With our activity done, let&#8217;s\u00a0work on\u00a0displaying the\u00a0scores. I&#8217;m going to open up\u00a0<strong>Project &gt; Variables<\/strong> and create a new variable called <em>scores<\/em>. This will be where we send our score data once we&#8217;ve determined what to display. I&#8217;ve also added a value of &#8220;n\/a&#8221; just so I&#8217;ll know if it&#8217;s not working right.<\/p>\n<p><a href=\"https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/12\/scoresvariable.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-84\" src=\"https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/12\/scoresvariable.jpg\" alt=\"scoresvariable\" width=\"518\" height=\"216\" srcset=\"https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/12\/scoresvariable.jpg 518w, https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/12\/scoresvariable-300x125.jpg 300w\" sizes=\"auto, (max-width: 518px) 100vw, 518px\" \/><\/a><\/p>\n<p>Next, let&#8217;s add a new slide and insert a text caption.\u00a0Now all you have to do to insert the variable into this text caption is go to your properties menu and select the <strong>[X]<\/strong> button in the Character section.<\/p>\n<p><a href=\"https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/12\/insertvariable1.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-85\" src=\"https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/12\/insertvariable1.jpg\" alt=\"insertvariable1\" width=\"300\" height=\"218\" \/><\/a><\/p>\n<p>This will bring up a popup window that lets you select our <em>scores\u00a0<\/em>variable from a dropdown menu. By default, the <strong>Maximum length<\/strong> is set to 50, but I&#8217;m going to bump that up some more just in case. It&#8217;s also worth spending a moment to make sure your text caption box (on the slide itself) is\u00a0big enough to accommodate your leaderboard at this point.<\/p>\n<p><a href=\"https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/12\/insertvariable2.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-86\" src=\"https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/12\/insertvariable2.jpg\" alt=\"insertvariable2\" width=\"411\" height=\"214\" srcset=\"https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/12\/insertvariable2.jpg 411w, https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/12\/insertvariable2-300x156.jpg 300w\" sizes=\"auto, (max-width: 411px) 100vw, 411px\" \/><\/a><\/p>\n<p>Now comes the fun part: select the slide and select <strong>Actions<\/strong> from the properties menu. <strong>On Enter<\/strong>, we&#8217;re going to\u00a0<strong>Execute Javascript<\/strong>. Click the <strong>Script_Window<\/strong> button and paste the following code:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">var search = ADL.XAPIWrapper.searchParams();\r\n\r\nsearch['activity'] = \"https:\/\/willchinda.com\/blog\/2015\/12\/06\/xapi-captivate-part-2\/\";\r\nvar res = ADL.XAPIWrapper.getStatements(search);\r\n\r\nvar stmts = res.statements;\r\n\r\nvar topscores = stmts.sort(function(a, b) { return a.result.score.raw &lt; b.result.score.raw ? 1 : -1; }).slice(0, 10);\r\n\r\nvar leaderboard = \"\";\r\n\r\nfor (var i = 0; i &lt; topscores.length; i++) {\r\n \r\n  var cleanName = JSON.stringify(topscores[i].actor.name);\r\n  var nameLength = cleanName.length;\r\n  cleanName = cleanName.slice(1,nameLength-1);\r\n\r\n  leaderboard += cleanName + \" scored \" + JSON.stringify(topscores[i].result.score.raw) + \"&lt;br \/&gt;\";\r\n}\r\n\r\nwindow.cpAPIInterface.setVariableValue(\"scores\",leaderboard);<\/pre>\n<p>Let&#8217;s unpack this and explain what&#8217;s going on in each section.<\/p>\n<p>For starters, we&#8217;re going to use xAPIWrapper&#8217;s search functionality, by creating a variable called\u00a0<em>search<\/em> that will store our parameters. We&#8217;re then going to assign our activity ID as a search parameter under &#8216;activity.&#8217; Then, we perform our search and save our results to the variable\u00a0<em>stmts<\/em>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">var search = ADL.XAPIWrapper.searchParams();\r\n\r\nsearch['activity'] = \"https:\/\/willchinda.com\/blog\/2015\/11\/30\/xapi-captivate-part-2\/\";\r\nvar res = ADL.XAPIWrapper.getStatements(search);\r\n\r\nvar stmts = res.statements;<\/pre>\n<p>Next, we have to sort our statements in descending order by\u00a0<em>raw score<\/em>. Then, we use the slice command to only keep the top 10 (if you want to show the top 5 or 20 or 78, just change that second number) and save it to a variable called\u00a0<em>topscores<\/em>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">var topscores = stmts.sort(function(a, b) { return a.result.score.raw &lt; b.result.score.raw ? 1 : -1; }).slice(0, 10);<\/pre>\n<p>Now comes the funky stuff. I&#8217;m going to start off by initializing a variable\u00a0<em>leaderboard<\/em> and assigning it the value of &#8220;&#8221; because I want to add strings to it.<\/p>\n<p>Then, I&#8217;m going to create a loop that will run as many times as there are elements in\u00a0<em>topscores<\/em> (in this case, the loop will run up to 10 times). Every time it runs, it&#8217;s going to pull out the actor&#8217;s name from\u00a0<em>topscores<\/em> and remove the quotes around it (it finds how many characters are in the name, saving it to <em>namelength<\/em>, then uses that to determine how many characters in to chop off). I&#8217;m not sure why the actor&#8217;s name always comes back with quotes around it, but for the sake of prettiness, I want to\u00a0chop them off.<\/p>\n<p>Finally, it takes the variable <i>leaderboard<\/i> that we initialized before the loop and adds the actor&#8217;s name, the word &#8220;scored&#8221; and their raw score. The cherry on top of all this is \u00a0<code class=\"EnlighterJSRAW\" data-enlighter-language=\"null\">\"&lt;br \/&gt;\"<\/code>which is a basic HTML tag that adds a linebreak after every line. Unfortunately, if you tried to publish this as a SWF, you&#8217;ll just end up seeing the tag instead of an actual line break! As I mentioned at the top, this is the advantage we have in publishing to HTML5.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">var leaderboard = \"\";\r\n\r\nfor (var i = 0; i &lt; topscores.length; i++) {\r\n \r\n var cleanName = JSON.stringify(topscores[i].actor.name);\r\n var nameLength = cleanName.length;\r\n cleanName = cleanName.slice(1,nameLength-1);\r\n\r\n leaderboard += cleanName + \" scored \" + JSON.stringify(topscores[i].result.score.raw) + \"&lt;br \/&gt;\";\r\n}<\/pre>\n<p>Finally, we&#8217;ll put the\u00a0<em>leaderboard<\/em> variable into our\u00a0<em>scores<\/em> variable so it shows up in our text caption.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">window.cpAPIInterface.setVariableValue(\"scores\",leaderboard);<\/pre>\n<p>And that&#8217;s it for our Captivate project! Let&#8217;s publish this and\u00a0set up our LRS endpoint and finish crafting the rest of our statement.<\/p>\n<p><a href=\"https:\/\/willchinda.com\/blog\/2015\/10\/18\/xapi-captivate-part-1\/\">Just like before<\/a>, I&#8217;m going to add a copy of <em>xapiwrapper.js<\/em> to the published folder and create\u00a0a new file called <em>setup.js.<\/em> With all the files\u00a0in place, the folder should look something like this:<\/p>\n<p><a href=\"https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/12\/leaderboardfiles1.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-87\" src=\"https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/12\/leaderboardfiles1.jpg\" alt=\"leaderboardfiles\" width=\"464\" height=\"262\" srcset=\"https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/12\/leaderboardfiles1.jpg 464w, https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/12\/leaderboardfiles1-300x169.jpg 300w\" sizes=\"auto, (max-width: 464px) 100vw, 464px\" \/><\/a><\/p>\n<p><em>[Note that unlike with SWF publishing, republishing an HTML5 project will replace\u00a0all the files in your output folder and delete additions you might have made prior. Make sure you have copies of whatever you&#8217;re copying into here!]<\/em><\/p>\n<p>Within\u00a0<em>setup.js<\/em>, I&#8217;ll setup my LRS endpoint (I&#8217;ll be using the ADL test LRS again) and define the <em>verb<\/em> and <em>object<\/em> properties\u00a0of the statement I&#8217;m sending. As mentioned in my requirements, I&#8217;m setting the verb to <em>completed<\/em> and again using the URL for this blog post as the object ID.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">function setupConfig() {\r\n var endpoint = 'https:\/\/lrs.adlnet.gov\/xapi\/';\r\n var user = 'xapi-tools';\r\n var password = 'xapi-tools';\r\n\r\n var conf = {\r\n   \"endpoint\" : endpoint,\r\n   \"auth\" : \"Basic \" + toBase64(user + \":\" + password),\r\n };\r\n\r\n ADL.XAPIWrapper.changeConfig(conf);\r\n}\r\n\r\n\/\/ Configure xAPIWrapper and save credentials\r\nsetupConfig();\r\n\r\n\/\/ Create a statement\r\nvar stmt = new ADL.XAPIStatement();\r\n\r\n\/\/ Add 'completed Captivate leaderboard tutorial' to the statement\r\nstmt.verb = new ADL.XAPIStatement.Verb('http:\/\/adlnet.gov\/expapi\/verbs\/completed', 'completed');\r\nstmt.object = new ADL.XAPIStatement.Activity('https:\/\/willchinda.com\/blog\/2015\/12\/06\/xapi-captivate-part-2\/', 'Captivate leaderboard tutorial');<\/pre>\n<p>For the finishing touch, all we need to do is add references to\u00a0<em>setup.js<\/em> and\u00a0<em>xapiwrapper.js<\/em> in our index file.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\">&lt;script src=\"xapiwrapper.min.js\" type=\"text\/javascript\"&gt;&lt;\/script&gt;\r\n&lt;script src=\"setup.js\" type=\"text\/javascript\"&gt;&lt;\/script&gt;<\/pre>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>And that&#8217;s it! If we run through the project a couple times with some random data, the leaderboard should look something like this:<\/p>\n<p><a href=\"https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/12\/leaderboardfinal.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-90\" src=\"https:\/\/willchinda.com\/blog\/wp-content\/uploads\/2015\/12\/leaderboardfinal.jpg\" alt=\"leaderboardfinal\" width=\"173\" height=\"91\" \/><\/a><\/p>\n<p>Just like before, I&#8217;ve <a href=\"https:\/\/willchinda.com\/xapi\/xapi-captivate2\/index.html\">posted a working version on my site here<\/a> and you can <a href=\"https:\/\/github.com\/willchinda\/captivatexapipart2\">download all of the files on github here<\/a>.<\/p>\n<p>Being able to read as well as write to an LRS from Captivate really opens up some possibilities, particularly when it comes to personalization and branching scenarios.<\/p>\n","protected":false},"excerpt":{"rendered":"<p class=\"excerpt\">So in my first post on the topic of integrating the Experience API and Captivate, I showed you how to take a couple user inputs and translate that into a simple statement that would be sent to an LRS when the user clicks a button. Sending a statement from a Captivate project is cool, but it&#8217;s only half the usefulness &#8230; <a class=\"read-more\" href=\"https:\/\/willchinda.com\/blog\/2015\/12\/06\/xapi-captivate-part-2\/\">Read More<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[21,11],"tags":[24,23,27,25,28,22],"class_list":["post-73","post","type-post","status-publish","format-standard","hentry","category-learning","category-technology","tag-adobe","tag-captivate","tag-javascript","tag-rapid-development","tag-tutorials","tag-xapi"],"_links":{"self":[{"href":"https:\/\/willchinda.com\/blog\/wp-json\/wp\/v2\/posts\/73","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/willchinda.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/willchinda.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/willchinda.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/willchinda.com\/blog\/wp-json\/wp\/v2\/comments?post=73"}],"version-history":[{"count":9,"href":"https:\/\/willchinda.com\/blog\/wp-json\/wp\/v2\/posts\/73\/revisions"}],"predecessor-version":[{"id":91,"href":"https:\/\/willchinda.com\/blog\/wp-json\/wp\/v2\/posts\/73\/revisions\/91"}],"wp:attachment":[{"href":"https:\/\/willchinda.com\/blog\/wp-json\/wp\/v2\/media?parent=73"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/willchinda.com\/blog\/wp-json\/wp\/v2\/categories?post=73"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/willchinda.com\/blog\/wp-json\/wp\/v2\/tags?post=73"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}