scorm to cmi5 - scorm xapi experience api elearning experts · the auth token is what you’re...
TRANSCRIPT
?
SCORM to cmi5An Implementor’s Guide
Bits To Convert
Manifest: What’s inside?Launch: Make it go!Runtime: Tell me about it!
What does the SCORM standard cover: Packaging (the ZIP file), Manifests (the loading instructions) and Runtime (the SCORM commands). The ZIP file is the same, so to convert from SCORM to cmi5, we have to map the manifests, change the launch mechanism, and rewrite the runtime commands. That doesn’t sound so bad, does it?
What’s a Manifest?
✴ Central component of content package
✴ Describes structure of material
✴ Indicates launchable components
✴ XML file
SCORM manifests answer the “where” part of e-learning packages. Where is my content segmented, where are the startup files located, where do I send the user to? If you’re familiar with SCORM 2004 manifest, with their fancy sequencing and navigation, that bottle of Jaeger probably looks pretty appealing right now. S&N is hard!
Manifest Comparisons
SCORM cmi5
Filename imsmanifest.xml cmi5.xml
Activity Tree SCO AU
Activities Activity Leaf Node AU
Containers in the Tree Aggregation Block
SCO = Sharable Content ObjectAU = Assignable Unit
Looking at how cmi5 stacks up, it has a pretty similar organization as SCORM does. Blocks are the containing elements that make up the navigation tree, and AUs are the leaf nodes that are launchable like SCORM SCOs.
SCORM 1.2 Manifest<?xml version="1.0" encoding="utf-8"?> <manifest identifier="_62yRKuq1asJ_course_id" version="1.0"> <metadata> <schema>ADL SCORM</schema> <schemaversion>1.2</schemaversion> </metadata> <organizations default="Simple_SCORM_Packager_Tutorial_ORG"> <organization identifier="Simple_SCORM_Packager_Tutorial_ORG"> <title>Simple SCORM Packager Tutorial</title> <item identifier="Simple_SCORM_Packager_Tutorial_SCO" isvisible="true" identifierref="__62yRKuq1asJ_course_id_RES"> <title>Simple SCORM Packager Tutorial</title> </item> </organization> </organizations> <resources> <resource identifier="__62yRKuq1asJ_course_id_RES" type="webcontent" href="index_lms.html" adlcp:scormtype="sco"> <file href="story_content/thumbnail.jpg"/> </resource> </resources> </manifest>
SCORM manifests have a few identifiable bits. The metadata block can tell us about which version of SCORM this is, the organizations for the clickable navigation tree in the LMS, and the resources for what it launches. So let’s overlay this onto a cmi5 XML file…
Convert to cmi5!
<?xml version="1.0" encoding="utf-8"?> <manifest identifier="_62yRKuq1asJ_course_id" version="1.0"> <metadata> <schema>ADL SCORM</schema> <schemaversion>1.2</schemaversion> </metadata> <organizations default="Simple_SCORM_Packager_Tutorial_ORG"> <organization identifier="Simple_SCORM_Packager_Tutorial_ORG"> <title>Simple SCORM Packager Tutorial</title> <item identifier="Simple_SCORM_Packager_Tutorial_SCO" isvisible="true" identifierref="__62yRKuq1asJ_course_id_RES"> <title>Simple SCORM Packager Tutorial</title> </item> </organization> </organizations> <resources> <resource identifier="__62yRKuq1asJ_course_id_RES" type="webcontent" href="index_lms.html" adlcp:scormtype="sco"> <file href="story_content/thumbnail.jpg"/> </resource> </resources> </manifest>
<?xml version="1.0" encoding="utf-8"?> <courseStructure xmlns="https://w3id.org/xapi/profiles/cmi5/v1/CourseStructure.xsd"> <course id="http://jcasolutions.com/courses/_62yRKuq1asJ_course_id"> <title> <langstring lang="en-US">Simple SCORM Packager Tutorial</langstring> </title> <description> <langstring lang="en-US">Simple SCORM Packager Tutorial</langstring> </description> </course> <au id="http://jcasolutions.com/courses/_62yRKuq1asJ_course_id/activity1"> <title> <langstring lang="en-US">Simple SCORM Packager Tutorial</langstring> </title> <description> <langstring lang="en-US">Simple SCORM Packager Tutorial</langstring> </description> <url>index_lms.html</url> </au> </courseStructure>
SCORM cmi5
cmi5 looks really similar, right? This single SCO example maps on pretty easily, with the top of the navigation tree in SCORM (the red box) becoming a Course Block in cmi5. The leaf node becomes an AU (the blue box), and the resource that maps to that leaf node becomes a <url> tag in the AU (the green box). Arguably, the cmi5 structure is more sensical, as it’s somewhat uncommon for SCO resources to be reused, and the amount of retyping is negligible.
Mastery score
✴ SCORM 1.2: <adlcp:masteryscore>100</adlcp:masteryscore>
✴ SCORM 2004:<imsss:minNormalizedMeasure>0.6</imsss:minNormalizedMeasure>
✴ cmi5:<au … masteryScore=“1.0">
Other Things To Convert
Mastery score is a common gotcha of SCORM; we hear regular confusion from customers about courses doing unexpected things when it’s in there. Good news, it’s been kept in cmi5!
Per-SCO/AU Parameters
✴ SCORM 1.2 and 2004: <item parameters=“a=1&b=2”>or <resource href=“...?a=1&b=2”>
✴ cmi5:<au> <launchParameters>{'a':1,'b':2}</launchParameters></au>
Other Things To Convert
cmi5 took a slight step forward and a slight step back on parameter management. Good news, no more query strings and worrying about when it gets escaped. Bad news, now you’re writing JSON, and LMSes are still going to make bad decisions about when to escape query strings. It’s also a slight limitation of the HTTP standard, because parameters are repeatable, but most JSON implementations don’t let you use the same key twice in an object. But it’s legal, I promise! Don’t do it.
Impedance Mismatch
Things there aren’t equivalents for:
✴ SCORM 1.2: None
✴ SCORM 2004:
✴ Sequencing
✴ Most parts of rollup
✴ “moveOn” or bust
Bad news for you die-hard SCORM 2004 folks: most of the things that SCORM 2004 brought to the manifest portion of the standard, there isn’t an option to move it forward. You’re going to have to simplify that logic somewhat significantly. For rollup, there’s a very simple “moveOn” command that you can specify passed or completed logic against, but there’s no way to have other AUs require them as prerequisites. It’s only good for other learning objects in your LMS, or if your LMS goes above and beyond the call of duty and adds flow support within a single cmi5 package.
Launch!
LMS Responsibilities
✴ Lots!
AU (your content’s) Responsibilities
✴ Get your auth token
✴ Only get it once!
Get the Launch Data
Launch
In SCORM, launching is wholly the LMS’s responsibility. In cmi5, it’s almost wholly the LMS’s responsibility. Your task is to get the data the course needs to know in order to make valid cmi5 statements later on, and handle some simple exit behavior.
Query parameters
✴ actor
✴ activityId
✴ endpoint
✴ fetch
✴ registration
Launch URL
These are the only 5 parameters that your AU has to care about, besides the ones you might use yourself. They’ll look something like…
http://www.enfixlp.com/ssptutorial/index_lms.html?endpoint=http://enfixlp.com/xapip/base/&fetch=http://enfixlp.com/xapip/authtoken?k=2390289x0&actor={"objectType":"Agent","account":{"homePage":“http://enfixlp.com","name":"gav"}®istration=760e3480-ba55-4991-94b0-01820dbd23a2&activityId=http://jcasolutions.com/courses/_62yRKuq1asJ
Launch URL
…this, in practice. This URL has them already decoded, what you see in your browser’s Location bar might not be so friendly to read. Let’s break it apart.
Launch URL Breakdown
http://www.enfixlp.com/ssptutorial/index_lms.html?endpoint=http://enfixlp.com/xapip/base/
&fetch=http://enfixlp.com/xapip/authtoken?k=2f39028d9
&actor={"objectType":"Agent","account":{"homePage":“http://enfixlp.com","name":“gav"}
®istration=760e3480-ba55-4991-94b0-01820dbd23a2
&activityId=http://jcasolutions.com/_62yRKuq1asJ
LRS URL
Auth URL
Learner
Registration ID in LMS
Course Activity ID
The LRS URL is the base path where your LMS commands will go. Statements will be written to that URL plus /statements will be appended on. The Auth URL is where your content can get any information you use to identify yourself to the LRS later on. The actor is the current learner. The registration is how the LMS keeps straight exactly which attempt this is, for which learner, for which course. The activity ID is the AU’s representation of what course you’re in.
HTTP POST to “fetch” URLEx: http://enfixlp.com/xapip/authtoken?k=2f39028d9
JSON response with token: {
"auth-token": “QWxhZGRpbjpvcGVuIHNlc2FtZQ==" }
Don’t call it again! {
"error-code": "1", "error-text": "The authorization token has already been returned." }
Getting the Auth Token
The auth token is what you’re going to put into every future LRS request; this is your secret key. You HTTP GET it once, and you get it only once. You’ll tuck this into the Basic Authentication HTTP header of your xAPI library of choice.
LMS-provided
✴ Describes context information for student launch
✴ Defines custom launch parameters
✴ Tells course where to go after completion
Stored as an xAPI Activity State
✴ Looked up by:
✴ activityId: The AU ID
✴ agent: The agent from the Launch URL
✴ registration: The registration from the Launch URL
✴ stateId: “LMS.LaunchData”
Launch Data
Launch data is what has all the bits that we need to do make the course run per the limitations of the cmi5 standard. It’s stored as a standard xAPI state.
{ "contextTemplate": { "contextActivities": { "grouping": [ { "objectType": "Activity", "id": "http://jcasolutions.com/courses/_62yRKuq1asJ/activity1", "definition": { "name": { "en-US": "Simple SCORM Packager Tutorial" }, "description": { "en-US": "..." } } } ] }, "extensions": { "https://w3id.org/xapi/cmi5/context/extensions/sessionid": "3738c07f-e8f2-46df-8d9e-11ccda3a191c" } }, "launchMode": "Normal", "returnURL": "http://enfixlp.com/courses/", "launchMethod": "AnyWindow", "launchParameters": "Start=1&FastForward=On", "entitlementKey": {"courseStructure": "xyz-123-9999", "alternate": "abc-456-1111"}, "moveOn": "CompletedOrPassed" }
Launch Data
Inside, we have the contextTemplate, which you’re going to see a lot of shortly in your cmi5 statements. The launchMethod tells the AU how to display the content, and the launchParameters what the content was opened as.
Runtime
So now it’s time for the hard part! The SCORM runtime is where all the heavy lifting is.
SCORM Runtime Environment Commands
SCORM 1.2 SCORM 2004
LMSInitialize(“”) Initialize(“”)
LMSSetValue(key, value) SetValue(key, value)
LMSGetValue(key) GetValue(key)
LMSFinish(“”) Terminate(“”)
Familiar commands from a familiar spec. These are the primary commands that you’ll have to replace with cmi5-equivalent operations.
Theoretically, any Internationalized Resource Identifier (IRI)
In practice: Usually known, well-defined verbs in IRI form
For this talk, verbs defined in the SCORM xAPI Profile:
✴ initialized
✴ terminated
✴ scored
✴ completed
✴ etc.
xAPI Verbs
Verbs communicate the action and intention of the statement.
Runtime, Compared
SCORM 1.2 SCORM 2004 xAPI / cmi5 verbs
LMSInitialize(“”) Initialize(“”) “initialized”
LMSSetValue(key, value)
SetValue(key, value)
(Mapped by key,HTTP POST/PUT)
LMSGetValue(key) GetValue(key) (Mapped by key,HTTP GET)
LMSFinish(“”) Terminate(“”) “terminated”
So when we throw cmi5 into the mix, here’s the rough equivalent statement mapping for those SCORM functions.
A cmi5 Session, Briefly
Step Number Action
Prequel Course is launched; launch data is gathered
0 Session opens: “initialized”
1 to (N-1) <One or more xAPI statements>
N Session closes: “terminated”
The cmi5 session represents the valid time in which we can send Statements during the learner experience.
Simple, just use the initialized verb
http://adlnet.gov/expapi/verbs/initialized
Let’s try it!
Initialized
How hard could this be?
{ "actor": { "objectType": "Agent", "name": "George Vilches", "account": { "homePage": "https://enfixlp.com/", "name": "gav" } }, "verb": { "id": "http://adlnet.gov/expapi/verbs/initialized", "display": { "en": "initialized" } }, "object": { "objectType": "Activity", "id": "http://jcasolutions.com/courses/_62yRKuq1asJ/activity1", "definition": { "name": { "en-US": "Simple SCORM Packager Tutorial" }, "description": { "en-US": "..." } } } }
Initialized
Here’s the new statement, you can see the initialized bit. Let’s send it to the LRS.
Uh oh…
Every one of you will be familiar with this screen soon enough. Chrome’s debugging panel is a gem.
Uh oh…
Valid xAPI != Valid cmi5
So maybe it’s not that simple. What was definitely a valid xAPI statement, is not a valid cmi5 statement.
“cmi5 defined”
✴ Known quantities
“cmi5 allowed”
✴ Fits within the session
“cmi5 not-allowed”
✴ Everything else
cmi5 Statements
cmi5 defined is where all the critical bits are at. Everything else is just squeezing in and around that primary workflow (or rejected outright from it).
“cmi5 defined”
Defined in the cmi5 xAPI Profile
“context” requires several parts
✴ LMS.LaunchData
✴ “contextTemplate”
✴ Include everything in every statement.
✴ Must have cmi5 category activity
✴ “id”: "https://w3id.org/xapi/cmi5/context/categories/cmi5"
{ "contextTemplate": { "contextActivities": { "grouping": [ { "objectType": "Activity", "id": "http://jcasolutions.com/courses/_62yRKuq1asJ/activity1", "definition": { "name": { "en-US": "Simple SCORM Packager Tutorial" }, "description": { "en-US": "..." } } } ] }, "extensions": { "https://w3id.org/xapi/cmi5/context/extensions/sessionid": "3738c07f-e8f2-46df-8d9e-11ccda3a191c" } },
cmi5 defined is a very strict set of rules for formatting xAPI statements, and you have to add in a bunch of extra parts for each type of statement. The contextTemplate has to be in there from the Launch Data, as well as a few activities.
Actor
✴ Must be objectType: “Agent”
✴ Must be “account” type
Limited verb choices
“id” required
“timestamp” required
“cmi5 defined” (cont’d)
Actors in xAPI can usually be all sorts of things, like Groups of people or e-mail addresses. cmi5 says they can only be the homepage and name pairing, under account. Statements also require an “id” even if you’re using the POST endpoint that wouldn’t require them. Same with timestamp.
Only usable within the session
✴ Between initialized and terminated
All other valid xAPI statements
“id” still required
“timestamp” still required
Recommended: xAPI SCORM Profile
“cmi5 allowed”
All the other things that can be done with xAPI that don’t conflict with the other cmi5 rules (like multiple passed/failed statements) can be done here, so long as you put the id and timestamp on the statement, and you do them within the initialized and terminated blocks. Let’s try initializing things again.
{ "id": "6f8e1135-c9fe-4e02-ae1f-a0f7468745eb", "actor": { "objectType": "Agent", "name": "George Vilches", "account": { "homePage": "https://enfixlp.com/", "name": "gav" } }, "verb": { "id": "http://adlnet.gov/expapi/verbs/initialized", "display": { "en": "initialized" } }, "object": { "objectType": "Activity", "id": "http://jcasolutions.com/courses/_62yRKuq1asJ/activity1", "definition": { "name": { "en-US": "Simple SCORM Packager Tutorial" }, "description": { "en-US": "..." } } }, "context": { "registration": "4eb0e063-669b-479a-86b3-f9be9ac88a1d",
"contextActivities": { "grouping": [ { "objectType": "Activity", "id": "http://jcasolutions.com/courses/_62yRKuq1asJ/activity1", "definition": { "name": { "en-US": "Simple SCORM Packager Tutorial" }, "description": { "en-US": "..." } } } ], "category": [ { "objectType": "Activity", "id": "https://w3id.org/xapi/cmi5/context/categories/cmi5" } ] }, "extensions": { "https://w3id.org/xapi/cmi5/context/extensions/sessionid": "c7b6f0a9-482c-4c03-acc1-548289126963" } }, "timestamp": "2016-06-09T15:34:26.887Z" }
Initialized, Take 2
Red = cmi5 required components
So here’s all the new bits! The context has all the additional components from LaunchData, and the new required bits.
User Statuses
Passed / Failed: exactly one, exactly once
Completed: exactly once
cmi5 defined has some very strict rules about passing and failing.
SCORM 1.2: LMSSetValue(“cmi.core.lesson_status”, “passed”)
SCORM 2004:SetValue(“cmi.success_status”, “passed”)
cmi5
✴ “result” key required
✴ “score” optional
✴ “duration” required
Passed / Failed
xAPI has a “result” object as part of the spec. Passed and failed statements have specific needs around the result.
Passed / Failed{ "id": "6f8e1135-c9fe-4e02-ae1f-a0f7468745eb", "actor": { "objectType": "Agent", "name": "George Vilches", "account": { "homePage": "https://enfixlp.com/", "name": "gav" } }, "verb": { "id": “http://adlnet.gov/expapi/verbs/passed", "display": { "en": "initialized" } }, "object": { "objectType": "Activity", "id": "http://jcasolutions.com/courses/_62yRKuq1asJ/activity1", "definition": { "name": { "en-US": "Simple SCORM Packager Tutorial" }, "description": { "en-US": "..." } } }, "result": { "success": true, "duration": ”PT9.55S” }, "context": { "registration": "4eb0e063-669b-479a-86b3-f9be9ac88a1d", "contextActivities": { "grouping": [ { "objectType": "Activity",
"id": "http://jcasolutions.com/courses/_62yRKuq1asJ/activity1", "definition": { "name": { "en-US": "Simple SCORM Packager Tutorial" }, "description": { "en-US": "..." } } } ], "category": [ { "objectType": "Activity", "id": "https://w3id.org/xapi/cmi5/context/categories/cmi5" } ] }, "extensions": { "https://w3id.org/xapi/cmi5/context/extensions/sessionid": "c7b6f0a9-482c-4c03-acc1-548289126963" } }, "timestamp": "2016-06-09T15:34:26.887Z" }
Here’s a minimum valid passed statement. Oof.
SCORM 1.2: LMSSetValue(“cmi.core.lesson_status”, “completed”)
SCORM 2004:SetValue(“cmi.completion_status”, “completed”)
xAPI
✴ “result” key required
‣ “score” optional
‣ “duration” required
Completed
Completed has pretty much the same rules.
Completed{ "id": "6f8e1135-c9fe-4e02-ae1f-a0f7468745eb", "actor": { "objectType": "Agent", "name": "George Vilches", "account": { "homePage": "https://enfixlp.com/", "name": "gav" } }, "verb": { "id": “http://adlnet.gov/expapi/verbs/completed", "display": { "en": "initialized" } }, "object": { "objectType": "Activity", "id": "http://jcasolutions.com/courses/_62yRKuq1asJ/activity1", "definition": { "name": { "en-US": "Simple SCORM Packager Tutorial" }, "description": { "en-US": "..." } } }, "result": { "completion": true, "duration": ”PT9.55S” }, "context": { "registration": "4eb0e063-669b-479a-86b3-f9be9ac88a1d", "contextActivities": { "grouping": [ { "objectType": "Activity",
"id": "http://jcasolutions.com/courses/_62yRKuq1asJ/activity1", "definition": { "name": { "en-US": "Simple SCORM Packager Tutorial" }, "description": { "en-US": "..." } } } ], "category": [ { "objectType": "Activity", "id": "https://w3id.org/xapi/cmi5/context/categories/cmi5" } ] }, "extensions": { "https://w3id.org/xapi/cmi5/context/extensions/sessionid": "c7b6f0a9-482c-4c03-acc1-548289126963" } }, "timestamp": "2016-06-09T15:34:26.887Z" }
So a valid completed statement isn’t any lighter.
*Record Scratch*
SCORM allows passed/failed to be sent repeatedly
Where’s the scores?
How do we only send the right one?
But now we have a problem. SCORM lets you spam these commands. The order you submit the keys in doesn’t matter. What do we do?
Hold the statements back
✴ Wait for cmi.{core.}score.raw
✴ Wait for the first “passed”
✴ Wait for LMSFinish / Terminate
Void the statements
✴ Requires good LRS support for voiding
No one-size-fits-all solution
More Impedance Mismatch
There’s a lot of potential ways to group the data, and none of them are going to be completely right for all cases. You have to know a bit about your content to make the right choice.
xAPI SCORM Profile
Interactions
Objectives
Custom xAPI statements
✴ So long as they’re “cmi5 allowed”
✴ Any verb
✴ cmi5 context template, no cmi5 category ID
What Else Can We Get?
The other xAPI bits we can shove into the cmi5 session, just stick to cmi5 allowed rules and you’ll be fine.
Just like initialized
Don’t send anything after this!
Redirect to LMS.LaunchData “returnURL”
Terminated
Unlike a SCORM package, where the LMS often makes the choice about what happens after LMSFinish(), It’s the AU’s job to exit correctly. Once you send that terminate command, sometime thereafter you have to do what LMS.LaunchData instructs you to.
Wrappers
This isn’t good candy, but maybe we can hide the nougat.
All sorts of things will get in your way with pre-existing content
✴ SCORM to cmi5 requires big course changes
✴ Weak publishing tool support for cmi5
✴ Course publishing JS hard to edit
✴ JS object required
✴ SCORM 1.2: API
✴ SCORM 2004: API_1484_11
Problems Converting Existing Content
Converting content is hard, especially if you didn’t originally build it.
Magic?
Intercept those calls!Replace them with cmi5!
What if we can provide a solution that doesn’t involve changing the content at all?
Create container HTML page
✴ iframe
✴ Don’t forget allowfullscreen tag for video!
✴ <iframe src="index.htm" width="100%" height="100%" style="position:absolute;top:0;bottom:0;left:0;right:0;overflow:hidden;" frameborder="0" scrolling="no" allowfullscreen></iframe>
✴ Popup
✴ Many LMSes already popup once
✴ Confusing experiences
Building a Wrapper
No matter which you choose, someone’s arbitrary content isn’t going to play nice. It’s best if you can support both.
Virtual SCORM API
Provide virtual JS objectSCORM 1.2 (API) or SCORM 2004 (API_1484_11)
Time to make a make LMS in the browser!
var API = { LMSInitialize: function(empty) { }, LMSFinish: function(empty) { }, LMSGetValue: function(key) { }, LMSSetValue: function(key, value) { }, LMSCommit: function(empty) { }, LMSGetLastError: function() { }, LMSGetErrorString: function(errorCode) { }, LMSGetDiagnostic(errorCode) { } };
SCORM 1.2 Virtual API
var API_1484_11 = { Initialize: function(empty) { }, Terminate: function(empty) { }, GetValue: function(key) { }, SetValue: function(key, value) { }, Commit: function(empty) { }, GetLastError: function() { }, GetErrorString: function(errorCode) { }, GetDiagnostic(errorCode) { } };
SCORM 2004 Virtual API
Initialize isn’t Special
Just put the Statement in there, and done
To Commit or To Immediate?
Commit(“”) requires a fully caching adapter.Don’t.
What we mean here is that if you want to do a perfect SCORM behavior mirror, you can’t immediately write even a single value, you have to wait for Commit() and Finish(). In practical terms, this means your implementation would have to be almost a fully conformant SCORM adapter, to correctly cache and validate things to hold them.For most cases, that’s probably overkill, and it’s best to just do the behavior when a SetValue() occurs. It’s not precise, but in our estimation it’s good enough for the 99%.
Convert calls as they come in
Cache/buffer as needed
✴ passed / failed (waiting for score)
✴ completed (waiting for passed / failed)
Some keys will need to be stored in different formats
✴ Ex: Session / Duration:
✴ SCORM 1.2: cmi.core.session_time: 0000:01:27
✴ cmi5: duration (ISO8601): PT0H1M27S
SetValue Conversion Challenges
Converting SetValue is where your troubles begin. This is where you need to have a decent understanding of the content you’re trying to wrap.
Write Twice, Read Never?
Where do we save these?Statements AND States!
Let’s use as much of the xAPI standard as we can!
Make Statements where useful
✴ cmi5 required (passed, failed)
✴ Likely reportables (cmi.interactions.n…)
Make States all the time, every SCORM key
✴ More on this in the GetValue section
Misses the read-only keys, but might be good enough
✴ If it’s not, you’ll have to build a fully conformant adapter; yuck!
SetValue Saves
Two approaches to fetching:
✴ Pull all potential values up front
✴ Fetch as you need them
Two approaches to storing:
✴ Fat Storage: All in one key
✴ Thin Storage: Each in their own key
GetValue Gets Expensive
Fat and thin storage are referring to the size of the object(s) being put into the LRS. Fat means that the JSON object itself is particularly large, relatively. Thin is because we compose lots of separate JSON objects.
SCORM Lives in the Present
SCORM is Synchronous!
Synchronicity is usually cited as anathema to the modern browser, but we’re working with a standard from 1998 here. Sometimes you have to give a little.
Steps:
1. Make a storage object
2. Make a list of all the “static” SCORM keys
1. Regular keys like cmi.core.score.raw
2. Count keys like cmi.interactions._count
3. Load these all from the state, store, maybe as separate keys?
4. Iterate through the iterable keys
1. Ex: cmi.interactions.n.[id, type, timestamp, weighting, learner_response, result]
2. Plus all the nested iterables:
1. Ex:cmi.interactions.n.objectives.n.id
GetValue Fetching Ahead of Time
This isn’t the easiest workflow, to be sure. You have to know the entire standard’s key offerings ahead of time, and it makes course initialization potentially very slow.
Fetch each key when you need it
Synchronous course activity means blocking HTTP requests
✴ AJAX without the first A!
Could cause stutter in courses that GetValue() often
GetValue Just In Time
If you get them along the way, the course can start a lot faster potentially, but it might jerk as synchronous calls get hit in critical locations.
Store all values in a single JSON object
Store in a single State
Pros:
✴ Single State Get gives you all data, less in-course stutter
✴ Much less network fetch overhead, especially for courses that reuse the same keys
Cons:
✴ Requires a smarter JS API object to manage keys
✴ Write network activity could be more expensive if you have a lot of keys; bandwidth based on lots of large requests of mostly unchanging data
GetValue Fat Storage
Store each value in a separate key
Pros:
✴ Can be fetched inline with course, if LRS and network are sufficiently fast
✴ For larger sets of keys, much less network write traffic
Cons:
✴ More likely to cause in-course stutter
GetValue Thin Storage
SetValue and GetValue, Summarized
Thin Storage Fat Storage
Low GetValue()Low SetValue() 👍 👍Low GetValue()High SetValue() 👍 👎High GetValue()Low SetValue() 🤷 👍High GetValue()High SetValue() 👎 👎
Emoji make everything better.Why the shrug there? Because it’s very dependent on your content and your LRS’s operation as to how that experience goes, mores than the other properties. You can technically make SetValue() async if you must (just return immediately and believe it worked), and that lets you get away with “High SetValue” being a thumbs up for thin storage, but there’s no way to do that for GetValue().
But can it chop, slice and dice?
Still no one size fits all solution
There’s no substitute for knowing how your content works in a SCORM context.
SCORM to xAPI:
✴ https://github.com/adlnet/SCORM-to-xAPI-Wrapper
SCORM to cmi5:
✴ https://github.com/JCASolutions/scorm_to_cmi5_wrapper
Open Source Wrappers
Thanks, ADL! We’ve done what we can to try to ease the pains around this, and we’re still experimenting with ideas to make it more configurable and flexible.
SCORM and cmi5 manifests are pretty much the same
Launch puts a little more work on the AU
cmi5 xAPI statements ask for a lot!
Consider using a cmi5 wrapper instead of modifyingyour course
Wrap Up
Questions?
Resources
cmi5 Specification: https://github.com/AICC/CMI-5_Spec_Current/blob/quartz/cmi5_spec.mdcmi5 Samples: http://aicc.github.io/CMI-5_Spec_Current/samples/xAPI SCORM Profilehttps://github.com/adlnet/xAPI-SCORM-Profile/blob/master/xapi-scorm-profile.mdCommon xAPI verbs:http://xapi.vocab.pub/describe/?url=https://w3id.org/xapi/adl
Contact info:
George [email protected]
321-296-8166 x203