Skip to content

SCORM SCOBot Documentation

Mark Statkus edited this page May 20, 2020 · 234 revisions

SCORM/SCOBot Documentation

About

First and foremost - this is not the SCORM 2004 Runtime Environment. This is a library for the Shareable Content Object. It enables the lookup of the SCORM 2004 RTE provided by the Learning Management System, and then provides controls, mechanisms, logging and failsafes to deal with that communication. If you are seeking the runtime environment see https://cybercussion.com.

4.x.x releases do not have a dependency on jQuery. This opened it up to more or other libraries and frameworks.

The SCOBotBase, SCOBot, and SCOBot_API_1484_11 were put together to assist anyone looking to implement SCORM in their content.

The ADL Specification(s) is rather large, and after several years working with it myself, I opted to try and get ahead of it last year after working with several projects that left me in a lurch. Anyone knows much of the physical JavaScript written in the early 2000's is (for a lack of a better term) tends to be hard on the eyes and global constants. Anyone that starts out with SCORM, thats worked with say Web Services or any other point to point communication, will be blissfully surprised how unfortunately boiled down the standard gets described. They'll tell you "just Initialize, Get/Set Value, Commit and Terminate". Sure, those are API's you can call, but they don't tell you much of what you have to do to get that to all work. So this is a manifestation of that, and my attempt to modernize the code along the way.

A minified and packed version are supplied for those that want a one-file-drop-in. 'packed' is insurance on servers that don't support gzipping. The encoding may potentially cost a little process decoding on the client, but I ultimately leave that up to everyones discretion. Most modern servers support gzipping so using the min file may be of interest. Bottom line minifying and merging the files saves you 60-80% depending on the implementation and gzip support.

Finally, there is no substitution for ADL's specification and documentation directly. Though I can say from personal experience, there are some discrepancies and minor misunderstandings that are very easy to get totally wrong. Because of this I'd like to thank Claude Ostyn's work, Rustici (scorm.com) for offering things to validate the work.

Implementation Choices

You can choose to run this commando. You'd only need to attach the 'js/scorm/SCOBotBase.js' (4.x.x) or 'js/scorm/SCORM_API.js'(in 3.x.x) to your project and write against the standard yourself. There are lots of checks and balances when you choose to do this. You will write code to manage the values your getting, how you'll handle them etc. The sequence in which you make the calls. The number of calls. This would be up front and intimate with the standard.

If you'd like the 'just add water' approach, I've written the 'js/scorm/SCOBot.js' to be used in conjunction with the 'js/scorm/SCOBotBase.js'. This will 'auto-pilot' its way thru initialization coming embedded with load and unload events. It will retrieve your Bookmarking, Suspended Data, Mode type, Entry type and manage your completion/success status and progress measure if you choose to let it. There are some configuration and extendable portions you may need to answer before you get started, but after you get past the default values you'll be set.

Lastly, you can take along the 'js/scorm/SCOBot_API_1484_11' if you choose. This will mimic the LMS so when you run this locally it will still 'talk' to what it thinks is the LMS. It won't be able to store or persist any of your data between sessions, but it gives you a eye into how things are behaving in your content prior to deploying it. Right now its not a full enforcement of the 2004 Specification, so you can break some rules, but it does a good job of tracing out the session's CMI object in the browser console as well as other messaging which aid in development. This opens it up to storing the attempt to local storage or some other endpoint if you go that direction.

Tip

It is very important before you begin interacting with SB or scorm namespaces that you listen to the 'load' event as highlighted below. If you attempt to start calling commands before the Runtime API is initialized you could run into undesirable results. This is especially important for all you folks using Flash, 3rd Party JavaScript frameworks or libraries. There is a sequence to this, and you might want to think of the SCORM Runtime API like a data source you need to wait for. This will get repeated a few more times.

Getting Acquainted

SCOBot is going to do a lot of automatic stuff. It will be taking care of load, and unload. It will self, start, and self exit. You can still supplement your SCO with exit, finish, submit, save, save for later or other types of buttons to give the user some control. If max_time_allowed is supported, be aware SCOBot will automatically start the timer. You can tell it to not do that, so you can control the view, and when it starts. The SCORM_API will auto commit on a Terminate call. So don't feel like you have to double down on commit calls. SCOBot will manage the exit type based on your default preferences, or if a time limit is hit. SCOBot also pools together launch data, comments from learner and student into objects, and as you'll see below will do time conversion to and from SCORM for you. Please be aware of the differences between ISO 8601 Timestamps and Duration. Those will be covered below, but rest assured its taking care of it. If you have any issues, or find any miscalculations within the project feel free to log a issue as others have done.

About unloading; SCOBot will send a unload event your SCO Player can listen to. That will allow you to wrap up calls you need to make, then SCOBot will fire your default exit, commit and terminate.

I wanted to note one issue that came up with "use strict" on a LMS that was using "callee.caller" fishing for what was calling the LMS API's. because of use strict that was blocking that request which was resulting in errors. Please feel free to comment those lines out if you're working with a legacy system and don't have control over them making those calls.

Getting Started

Implementation into your HTML

These <script> tags can be added to your presentation layer to include them in your project(s).

4.x.x

<script type="text/javascript" src="js/scorm/SCOBotUtil.js"></script>
<script type="text/javascript" src="js/scorm/SCOBot_API_1484_11.js"></script>
<script type="text/javascript" src="js/scorm/SCOBotBase.js"></script>
<script type="text/javascript" src="js/scorm/SCOBot.js"></script>

3.x.x

<script type="text/javascript" src="js/scorm/Local_API_1484_11.js"></script>
<script type="text/javascript" src="js/scorm/SCORM_API.js"></script>
<script type="text/javascript" src="js/scorm/SCOBot.js"></script>

or if you want to use the built (packaged) file -

<script type="text/javascript" src="js/scorm.bot.pack.js"></script>

Implementation into your HTML or JavaScript

I've included the name space and some of the options you can pass into these when they are instantiated.

// var scorm = new SCORM_API ({                // in 3.x.x
var scorm = new SCOBotBase({                   // in 4.x.x
        debug         : true,                  // true or false
        time_type     : "GMT",                 // UTC, GMT or ""
        preferred_API : "findAPI",             // findAPI, findSCORM12, findSCORM2004  ** new in 4.0.9 **
        exit_type     : 'suspend',             // suspend, finish
        success_status: 'unknown',             // passed, failed, unknown
        cmi           : your_own_runtime_data  // optional way to pass in a customize runtime object for non-LMS
    }),
    SB    = new SCOBot({
        interaction_mode      : 'state',      // state (single interaction) or journaled (history of interactions)
        scaled_passing_score  : '0.7',        // uses cmi.score.scaled to equate cmi.success_status
        completion_threshold  : '1',          // uses cmi.progress_measure to equate cmi.completion_status
        initiate_timer        : false,        // if max_time_allowed, you can set the timer vs. SCOBot
        scorm_strict          : true,         // Setting this to false will turn off the attempts to truncate data that exceeds the SPM's commonly supported by LMS's.
        // New in 4.0.3 -
        base64                : true,    // Set to false if you manage suspend data or do not wish for it to be encoded (v4.0.3 option)
        happyEnding           : true,         // Set to false if you want to disable this method (v4.0.5)
        useJSONSuspendData    : true,         // Set to false if you manage suspend data (v4.0.3 option)
        // New in 4.0.9 -
        doNotStatusUntilFinish: true          // Set to true if you don't want to update the score until finished. (4.0.9 option)
        sequencing: {                         // New in 4.1.1 : Sequence and Navigation options
                nav: {
                        request: '_none_'     // Change to continue, previous, choice{target=ID}, exit, exitAll, abandon, abandonAll, suspendAll for more options to auto-navigate after Termination.
                }
           }
        }
    });

The above could be within <script> tags in your HTML, or put into a javascript file in your project. Each setting has inherent meaning to how your Shareable Content Object (SCO) will utilize the features. By setting debug to false, you'll no longer get console messages in the browsers inspector. By changing the 'exit_type' you may want to finish, instead of suspend. This all depends greatly on what default behaviors your after, vs. the ones provided.

There are a few events you should listen for in SCOBot so you can tell your SCO Player to similarly kick it's processes off. Keep in mind my examples below are meant for you to modify. I typically have a SCO Player or navigation that controls pages in a SCO, but you may just have a single page.

// Load (ready) event
//$(SB).on('load', function (e) {                     // in 3.x.x
SCOBotUtil.addEvent(SB, 'load', function(e) {         // in 4.x.x
	// SCOBot already took care of connection to the LMS and Initialize!
	// Do everything else you would do to fire up or resume your content here.
	player = new SCOPlayer(); // or player.init();  You customize this!
	return false;
});
// Unload Event
//$(SB).on('unload', function (e) {                   // in 3.x.x
SCOBotUtil.addEvent(SB, 'unload', function(e) {       // in 4.x.x
	// You customize this to fit your needs!
	player.exit();                                // Tell your SCO Player to wrap up?
	//SB.setBookmark(player.getCurrentPage());    // Set the current bookmark?
	//SB.happyEnding();                           // Force student to passed, completed and 100%?
	return false;
});
// Resume Event
// Allows you to prompt the user if they want to resume a prior session (optional)
SCOBotUtil.addEvent(SB, 'resume', function(e) {       // in 4.1.6
        // You customize this to fit your needs!
        // Modal or prompt?
        // On "no" SB.set('suspend_data', '{pages: []}');
        // And you may want to empty the scorm value to scorm.setvalue('cmi.suspend_data', "");
        // And you may even want to ignore the 'cmi.location' aka bookmark.
        // On "yes", business as usual
});

// Message Event *optional*
//$(SB).on('message', function (e) {                   // in 3.x.x
SCOBotUtil.addEvent(SB, 'message', function(e) {       // in 4.x.x
	// Required by time_limit_action, if its "message" you need to present the student with it
	player.showMessage(e.text); // Time Limit Exceeded!, or do your own.
	return false;
});
// Continue Event *optional*
//$(SB).on('continue', function (e) {                 // in 3.x.x
SCOBotUtil.addEvent(SB, 'continue', function(e) {       // in 4.x.x
	// Required by time_limit_action, if its "continue" you may need to make a choice what to do.
	player.continue(); // Advance past a page?  Do nothing?  Up to you.
	return false;
});
// Comments from LMS *optional*
//$(SB).on('comments_lms', function(e) {                    // in 3.x.x
SCOBotUtil.addEvent(SB, 'comments_lms', function(e) {       // in 4.x.x
	//e.data will return {Array} of {Object(s)}
});
// Exception Event
//$(SB).on('exception', function (e) {                   // in 3.x.x
SCOBotUtil.addEvent(scorm, 'exception', function(e) {       // in 4.x.x
	// Do something to notify the student there was a exception (SCORM Failure)
	// This is thrown when the LMS becomes unresponsive or not compliant with SCORM 2004.  Data loss is possible.
	return false;
});

// Optional: Alert user of no LMS Connectivity (this event fires after 1 second)
SCOBotUtil.addEvent(scorm, 'no_lms', function(e) {       // in 4.x.x
	alert(e); // Or custom modal/toast etc ...
});

// Optional for locally logging get/set value. (very chatty in log)
//$(scorm).on('setvalue, getvalue'), function(e) {       // in 3.x.x
SCOBotUtil.addEvent(scorm, 'setvalue', function(e) {     // in 4.x.x
     /* returns:
     {
       n : 'namespace',
       v : 'value',
       error: {
           code: 'error code',
           message: 'The error message',
           diagnostic: 'Diagnostic from lms'
       }
     }
     */
	// route to your log.  Remember SCOBot will trace out real errors in the console if debug is enabled.
});

You can place this code similarly in the HTML or JavaScript portions of your code set.

SCOBotBase Options

  1. debug : default is false. If set to true, will trace messages in the browser console.
  2. throw_alerts : default is false. This will throw JavaScript alert messages when errors are found.
  3. exit_type: default is 'suspend'. You can set this to 'finish' to essentially complete the SCO on exit.
  4. time_type: default is 'GMT'. You can set this to 'UTC' or empty ''. This will adjust the time format used by Interaction timestamps.
  5. preferred_API : default is 'findAPI', but access 'findSCORM12' and 'findSCORM2004'
  6. use_standalone : default is true. If you set this to false, it won't use the Local_API_1484_11 (LMS mimic)
  7. cmi: default is null. If you have a non-LMS solution, you can force runtime data similar to that found in Local_API_1484_11.js with any threshold, behavior changes. Or this is where you'd push in the runtime data retrieved from local storage, cookie or server call.

SCOBot Options

  1. interaction_mode : default is 'state'. Can switch to 'journaled' for interaction history. Verify your LMS or target LMS can support this.
  2. launch_data_type : default is 'querystring'. I let you also specify json, however the specification doesn't mention allowing it.
  3. scorm_edition : default is '3rd'. You could set this to 4th. This is mostly a 'just in case' as far as specification support switching.
  4. completion_threshold : default is 0. You could set this to a range between 0 and 1 that you want to default to. This will be overridden by a value coming from the LMS however (if one is available). I can't tell you what complete is, your team must decide this.
  5. scaled_passing_score : default is 0.7. You could set this to a range between 0 and 1 that you want to default to. This will determine the students 'passed' or 'failed' success_status based on calculating the cmi.score.scaled value (lesser or greater than).
  6. Using Flash as a presentation layer? You just have to be careful because some rogue characters can mess up the XML communication that is used by ExternalInterface. Escape or encode characters if this is a problem.
  7. initiate_timer: default is true. You can set this to false if you want to kick the timer off yourself. Like if you want to make sure everything is fully loaded before you start it. Else SCOBot will manage it the moment it finishes the 'start' method post load.
  8. scorm_strict: default is true. Certain times you may exceed limits accidentally. If this happens, data gets lost. This is a ongoing effort as of March 2013 to begin to truncate areas of common problems so you don't lose student data or interactivity. If you don't care about this, set it to false.
  9. base64: default is true. SCOBot will encode suspend data and decode suspend data using atob/btoa native functionality present in modern browsers. IE < 10 will have issues with this, so either a 3rd Party code library that adds atob/btoa functionality will be needed, or you can turn this off. This simply obfuscates the suspended data so its less readable to the naked eye.
  10. useJSONSuspendData: default is true. SCOBot will use JSON.stringify and parse to re-establish structured suspend data. If your content player manages this, turn this feature off. Also JSON support will need to be added for IE < 9. There are 3rd party libraries via JSON.org for javascript.
  11. doNotStatusUntilFinish: default is false. If set to true SCOBot will set score, status on a finsh/timeout call. This is a fix for certain platforms that re-launch your content in review mode if 'graded'. This is simply a workaround for situtations when the need arises.

Injecting SCOBot Into a Page

Say you have a page that didn't previously support SCORM. At minimum it needs to initialize, set score, status, completion, commit and terminate. Example below is a quickly to make that happen.

<!-- Inject into HTML Pages to make SCORM compliant -->
<script src="js/scorm.bot.pack.js"></script>
<script>
    var scorm = new SCOBotBase({ 
            debug: false,                  // true or false
            time_type: "GMT",              // UTC, GMT or ""
            exit_type: 'finish',           // suspend, finish
            success_status: 'passed',      // passed, failed, unknown
            completion_status: 'completed' // completed, incomplete, not attempted, unknown
        }),
        SB    = new SCOBot({
            interaction_mode: 'journaled',  // state or journaled
            scaled_passing_score: '0.7',    // uses cmi.score.scaled to equate cmi.success_status
            completion_threshold: '1'      // uses cmi.progress_measure to equate cmi.completion_status
        });

    SCOBotUtil.addEvent(SB, 'load', function(e) {
        // SCOBot is ready, lets auto-score
        SB.happyEnding();
        //SB.commit(); // SCOBot will Commit on Terminate.  Auto event on unload.
    });
</script>
<!-- End -->

Pre-Flight

Not all platforms are created equal. Some physically expose multiple runtime API's at the same time. SCOBots default behavior is to look for SCORM 2004, then SCORM 1.2 and if it cannot locate it, fail over locally (no data persistence). You may need to physically specify your preferred API to 'findSCORM12' or 'findSCORM2004' if this happens. Some platforms even translate the Runtime calls, but some don't.

It's very important you make considerations about features and support you're going to look into deploying your content on a Learning Management Server. Not every LMS is created equal, so you'll want to get a base pulse on what SCORM version they support, as this will either narrow or expand the scope of what you'll be able to do. The differences in SCORM 1.2 and 2004 are highly notable, and there are subtle differences in some of the namespaces available.

  1. What SCORM version/edition does your target LMS Support?
  2. If you're using Interactions, do you want them Journaled, or updated (state)? Interactions and Objectives within the specification allow us to record several interaction types, latency, correct response patterns, responses and result. Great for quiz's, tests among others.
  3. Puzzled about whats supported? Take the QUnit (html) files and reference them in a imsmanifest.xml. Upload them to your LMS and see if any tests fail. That will give you an idea what your in store for with your production content. LMS's should adhere to the standard, don't let them water down your content or make it less portable.
  4. Think about the type of content your offering. If its mostly presentations or things that aren't necessarily scored, you may not be suspending any data. You can set the 'exit_type' to finish and the 'success_status' to passed. This will ensure the student is marked completed and scored properly after they view it.
  5. If your having issues, most modern browsers have a inspector now that allows you to view console messages. If 'debug' is true, you can see the trace messages coming from these API's and the data they are sending.
  6. You may be running content that will not connect to a LMS. Due to this, there is a way to check whether or not you actually connected to a LMS. scorm.isLMSConnected() will return true or false. This can allow you to show/hide navigation or other features you want to hide from the user.

Launch Data vs. Launch Parameters

This can often be confusing. I've opted to describe this the following way, but it is not the THE way (my interpretation). With-in a imsmanifest.xml, you can enter in Launch Parameters used by the SCO in order to alter its default behavior. You could pass in things like 'theme=green' or 'grade=11' etc ... This just gives you options. Similarly you can pass in Launch Data via SCORM which allows your content to make a 'cmi.launch_data' call to retrieve it. This is commonly a 1000 character string in the form of a QueryString like '?name=value&interaction_mode&=journaled'. I decided to make Launch Parameters more of a SCO Player level behavior(themes, flavors, options), and Launch Data more of a SCORM level behavior (SCORM modes, settings). That way there is a line in the sand and not any cross contamination. You can however, use it how you want to. Main thing is your adding more options to a SCO to make it more diverse.

API Support

Start (default)

SB.start(); // Note: SCOBot does this automatically

Default Behavior triggered on 'load' event of Browser Window. This will begin to retrieve the Mode, Entry, Bookmark, Suspend Data, success, completion status and will turn the Launch Data into an internal object.

About single calls direct to LMS aka 1:1 calls

From time to time you may find yourself needing to set/get a value thats not supported by a SCOBot API directly. I felt it wasn't in my best interest to make a renamed API call for a single 'cmi.x' type request. To do that, do the following:

SB.setvalue('cmi.learner_preferences.audio_level', '0.4'); // Beware of the specification character limits and RO/WO access
//or
SB.getvalue('cmi.learner_preferences.audio_captioning');

Depending on your implementation you may do this a bit. If you find yourself actually having to code logic in your content, send me a email or branch the project and feel free to add the given functionality.

Set Totals

This is required if you are using Interactions and or Objectives. The internal Progress of the SCO will be calculated so it can set cmi.score.scaled, raw, min and max. As well as cmi.progress_measure, completion_status, and success_status. If the total number of Objectives is not supplied it will result in a console error/warning. As a prerequisite you may need to count these values up at creation or author time so that they may be properly set at the beginning.

SB.setTotals({
	totalInteractions: '0',
	totalObjectives: '0',
	scoreMin: '0',
	scoreMax: '0'
});

Mode (normal, browse, review)

SB.getMode();

This will let you know what mode your Shareable Content Object (SCO) is in. This is normally important incase you want to disable entry or behave differently.

Entry (ab-initio, resume, "")

SB.getEntry();

Some LMS's may not support this and just return an empty string. Because of this, I coded SCOBot to determine if it got suspend data to put it in a locally set 'resume' mode so its more reliable. This can stop you from doing a number of things you'd do if you've already done them previously in another session.

Start Timer

If you decided to start the timer your self, for SCO's that make use of max_time_allowed, you can kick this off yourself. Note, you can't stop this once it starts

SB.startTimer();

Based on the time_limit_action, you will either exit, or continue. You will either get a message, or no message. So if you use this feature, understand that SCOBot will fire messages informing you to take action at your player level. SCOBot makes no attempt to pop alert boxes. SCOBot will fire a event like unload, if we are commanded to exit, so you can wrap up your player activities, then terminate.

Get Comments From LMS

This is somewhat of a interesting gem in the specification where the SCO has read-only access to see if the Teacher made some comments about the content they are viewing. They left space for a 'location' which could imply the page that they are viewing, however the LMS has no real vision into the contents page or page id that its viewing. Maybe a teacher could type one in, or maybe its somehow tied to values in a imsmanifest... Never-the-less, if the LMS supported the ability for the teacher to make some comments to the student I set this up to build out an array of comments for you to display in your SCO Player.

SB.get('comments_from_lms');
// or as an event you can listen to
$(SB).on('comments_lms', function(e) {
    //e.data will return {Array} of {Object(s)}
});

// This will return
[
  {
    comment: 'str value',  {String}
    location: '2',         {String}
    timestamp: '2012-03-06T04:06:54.0-08:00' {ISO 8601 Timestamp}
  }
  x N
]

Set Comment From Learner

The learner is allowed to make comments that are incremented (managed by the LMS). These include a comment string, a location and a time stamp.

var dateObj = new Date();
SB.setCommentFromLearner('Message', 'Location', dateObj);

Set Bookmark

This will allow you to set up to 1000 characters for a Bookmark. This could be as simple as a number, or as rich as something further. Its up to you.

SB.setBookmark('value');  // {String} up to 1000 chars

Get Bookmark

This will return a Bookmark you previously set. Check your mode to see if you need to do it.

SB.getBookmark();  // Returns {String}

Grade It

So, if you set cmi.score.raw, min and max and update cmi.progress_measure as they flip thru your Shareable Content Object, you can call SB.gradeIt() to set the cmi.completion_status, cmi.success_status, and cmi.score.scaled so you don't have to bury that code in your content. Please note, you need to pass scaled_passing_score and completion_threshold defaults into SCOBot to use this feature if these values will not be present in a imsmanifest.xml (CAM Package).

SB.gradeIt();  // Returns {String}

Happy Ending

I realize not everyone does super rich or complex scoring, and by popular demand I offer a happy ending... This will auto-score the student to passed, completed and reflect a 100% score. You'll want to call this on some criteria that they've watched your movie past a certain point, or answered a question, or visited the content you provided. Its up to you. If you use Objectives, and Interactions below, SCOBot does scoring based on this so be aware that forcing completion may conflict with SCOBot's evaluation of their score.

SB.happyEnding();  // Returns {String}

Set Suspend Data By Page ID

You can store a native data object per page. This can be as simple as some name/value pairs, or as rich as objects, arrays etc ... All data is stored to an Suspend Data Object then via JSON is encoded to a string when exiting the SCO automatically. Data is either added or updated based on the Page ID.

SB.setSuspendDataByPageID(id, title, data);  // id: {String} Page ID, title: {String} "Presentation", data: {Object}

Get Suspend Data By Page ID

This method will return only the data object back based on the Page ID.

SB.getSuspendDataByPageID(id);  // returns {Object} or 'false'

Set Objective

If you are doing assessments, quiz's or have trackable interactions you'll be setting some defined by SCORM 2004. These can be linked to Interactions (covered later). You'll be responsible for constructing the objective either based on hand coded or authored values. You may be confused by what some of these are. That's totally understandable. The objective's scaled score is the result of doing some math: score.scaled = (raw - min) / (max - min). Note you may have a division by zero issue. The success_status is normally calculated by checking to see if the score.scaled, is greater than the default scaled_passing_score. There may be a case where scaled_passing_score isn't provided so you have to default to something. But thats how 'passed' or 'failed' is figured out. progress_measure at this level would typically be the amount completed in the objective, divided by the total things you could do in this objective (might just be 1). Lastly, completion_status, would be the act of checking to see if progress measure exceeds the completion_threshold either defaulted at the highest level, or if you've made a overriding value available at this low level (not supported by SCORM directly). Now you might be thinking, why didn't you do the math for me? It's because SCORM doesn't support completion_threshold and scaled_passing_score at this level that I can't assume these things. You can have values set at these levels because they differ from the highest level.

SB.setObjective({
   id: '1_1',                                             // {String}
   score: {                                               // {Object}
	   scaled: '0',                                       // {String}
	   raw: '0',                                          // {String}
	   min: '0',                                          // {String}
	   max: '1'                                           // {String}
   },
   success_status: 'unknown',                             // {String} passed, failed, unknown
   completion_status: 'not attempted',                    // {String} completed, incomplete, not attempted
   progress_measure: '0',                                 // {String}
   description: 'The answer is a true false interaction'  // {String}
}); 

Note: Updating an Objective

You only have to send the items that updated for an objective and don't have to resend the whole object like above. One common mistake I've seen over the years, is developers take a shortcut and just keep re-posting unchanged information. So an efficiency here to limit the SCORM chatter would be to only update the items that have changed.

Get Objective

This will return your objective if it exists. It will reconstruct the object your originally passed above or respond 'false' letting you know that the objective doesn't exist.

SB.getObjective('id');  // id: {String} returns {Object} or 'false'

Set Interaction

Interactions (and there are a few types) can be recorded to show a timestamp, latency, correct response patterns, responses, results etc ... The format of these can get interesting because based on each interaction type the behavior or formatting changes. I didn't want everyone to be subjected to this, so I rolled up a API to allow you to pass an object much like setObjective. However, due to the diversity of interaction types (true-false, choice, matching, fill-in, long-fill-in, sequencing, likert, numeric other) I have to break down what SCOBot is expecting per. For Interaction(s) timestamp its marked using a ISO 8601 time stamp. Based on the selection above that will be recorded in GMT (From GMT) or UTC (at GMT) or default to a base timestamp free from TZD +/- GMT, or a Z designating UTC. Latency is recorded in a PTHMS based format. I allow you to just pass the date objects from your interactions instead of being bothered with this. If you pass a start time (timestamp) and a end time (latency) I will calculate and format latency for you. Please be mindful of each interaction type, as the formats for what they are expecting differ from type to type. If you encounter any problems, its most likely due to passing a object where an array was expected as an example. Console log should help you clear up any issues, since there is a bit of data to convey.

Please keep in mind the values that exceed the SPM (Smallest Permitted Maximum) could knock out you of compliance which in turns shaves down your portability to other LMS systems. You can make decisions to break these, if you know the LMS supports more. The LMS may truncate the values, as a result. ADL makes mention that this is the lowest number they want people to support, but they can choose to support a higher number.

True False

SB.setInteraction({
	id: '1',                             // {String}
	type: 'true-false',                  // {String}
	objectives: [                        // {Array}
		{                                // {Object}
			id: '1_1'                    // {String}
		}
	],
	timestamp: startTime,                // {Object} date start
	correct_responses: [                 // {Array} ** Only supports one correct answer **
		{                                // {Object}
			pattern: 'true'              // {String} true or false 
		}
	],
	weighting: '1',                      // {String}
	learner_response: 'true',            // {String} true or false
	result: 'correct',                   // {String} correct, incorrect, neutral
	latency: endTime,                    // {Object} date end (optional)
	description: 'This is the question?' // {String} question commonly
});

Note: Updating an Interaction

You only have to send the items that updated for an interaction, and don't have to resend the whole object like above. One common mistake I've seen over the years, is developers take a shortcut and just keep re-posting unchanged information. In some cases with interactions this results in SCORM Errors to be triggered because your attempting to align to objectives already posted. So an efficiency here to limit the SCORM chatter would be to only update the items that have changed.

Choice

The order of short identifiers in the answer (pattern or response) is insignificant This is a lot like Sequence, but this is where they have distinction

SB.setInteraction({
	id: '2',                                     // {String}
	type: 'choice',                              // {String}
	objectives: [                                // {Array}
		{                                        // {Object}
			id: '2_1'                            // {String}
		}
	],
	timestamp: startTime,                        // {Object} date start
	correct_responses: [                         // {Array} ** Only supports 10 patterns **
		{                                        // {Object}
			pattern: ["a","b"]                   // {Array} of short identifier types SPM: 10, max length of 36
		}
	],
	weighting: '1',                              // {String}
	learner_response: ["a","c"],                 // {Array}
	result: 'incorrect',                         // {String} correct, incorrect, neutral
	latency: endTime,                            // {Object} date end
	description: 'Which choices would you pick?' // {String} question commonly
});

Matching

SB.setInteraction({
	id: '6',                                                // {String}
	type: 'matching',                                       // {String}
	objectives: [                                           // {Array}
		{                                                   // {Object}
			id: '6_1'                                       // {String}
		}
	],
	timestamp: startTime,                                   // {Object} date start
	correct_responses: [                                    // {Array}
		{                                                   // {Object}
			pattern: [                                      // {Array} ** Only supports 5 patterns **
				["tile_1", "target_2"],                     // {Array} of {String} Short Identifiers SPM 10 with a max length of 36
				["tile_2", "target_1"],
				["tile_3", "target_3"]
			]
		}
	],
	weighting: '1',                                         // {String}
	learner_response: [                                     // {Array}
				["tile_1", "target_2"],                     // {Array} of {String}s
				["tile_2", "target_1"],
				["tile_3", "target_3"]
			],
	result: 'correct',                                      // {String} correct, incorrect, neutral
	latency: endTime,                                       // {Object} date end (optional)
	description: "Place these steps in the matching order." // {String} question commonly
});

Sequencing

The order of short identifiers in the answer (pattern or response) is significant

SB.setInteraction({
	id: '5',                                    // {String}
	type: 'sequencing',                         // {String}
	objectives: [                               // {Array}
		{                                       // {Object}
			id: '5_1'                           // {String}
		}
	],
	timestamp: startTime,                       // {Object} date start
	correct_responses: [                        // {Array} ** Only supports 5 patterns
		{                                       // {Object}
			pattern: ["c","b","a"]              // {Array} Short Identifiers SPM 10, max length 36
		}
	],
	weighting: '1',                             // {String}
	learner_response: ["a","c","b"],            // {Array}
	result: 'incorrect',                        // {String} correct, incorrect, neutral
	latency: endTime,                           // {Object} date end (optional)
	description: 'Place these options in order' // {String}
});

Fill In

SB.setInteraction({
	id: '3',                                       // {String}
	type: 'fill-in',                               // {String}
	objectives: [                                  // {Array}
		{                                          // {Object}
		   id: '3_1'                               // {String}
		}
	],
	timestamp: startTime,                          // {Object} date start
	correct_responses: [                           // {Array}
		{                                          // {Object}
		   pattern: {                              // {Object}
			   case_matters: true,                 // {Boolean} (optional)
			   order_matters: true,                // {Boolean} (optional)
			   lang: 'en',                         // {String} 2 or 3 letter lang code (optional)
			   words: ["car","automobile"]         // {Array} of {String}s
			}
		}
	],
	weighting: '1',                                // {String}
	learner_response: {                            // {Object}
		lang: 'en',                                // {String} 2 or 3 letter lang code
		words:["car","automobile"]                 // {Array} of {String}s
	},
	result: 'correct',                             // {String} correct, incorrect, neutral
	latency: endTime,                              // {Object} date end
	description: 'Which choices would you pick?'   // {String} question commonly
});

Long Fill In

SB.setInteraction({
	id: '4',                                               // {String}
	type: 'long-fill-in',                                  // {String}
	objectives: [                                          // {Array}
		{                                                  // {Object}
			id: '4_1'                                      // {String}
		}
	],
	timestamp: startTime,                                  // {Object} date start
	correct_responses: [                                   // {Array}
		{                                                  // {Object}
			pattern: {                                     // {Object}
				lang: 'en',                                // {String} lang code (optional)
				case_matters: false,                       // {Boolean} (optional)
				text: "It's been a long day"               // {String}
			}
		}
	],
	weighting: '1',                                          // {String}
	learner_response: {                                      // {Object}
		lang: 'en',                                          // {String} lang code (optional)
		text: "There was one, but it's been a long day."     // {String}
	},
	result: 'correct',                                       // {String} correct, incorrect, neutral
	latency: endTime,                                        // {Object} date end (optional)
	description: 'Which choices would you pick?'             // {String} question commonly
});

Performance

SB.setInteraction({
	id: '7',                                            // {String}
	type: 'performance',                                // {String}
	objectives: [                                       // {Array}
		{                                               // {Object}
			id: '7_1'                                   // {String}
		}
	],
	timestamp: startTime,                               // {Object} date start
	correct_responses:  [                               // {Array} Support for 5
		{                                               // {Object}
			pattern: {                                  // {Object}
 				order_matters: false,                   // {Boolean}
				answers: [                              // {Array} Up to 125 choice(10), literal(250)
  					["step_1", {min: 5, max: 6}],       // {Array} that accepts {String} or {Object} number range
					["step_2", "answer_1"],
 	 	 	 	 	["step_3", "answer_3"]
 	 	 	 	]
			}
		}
	],
	weighting: '1',                                     // {String}
	learner_response: [                                 // {Array}
		["step_1", "5.148"],
		["step_2", "answer_1"],
		["step_3", "answer_3"]
	]
	result: 'correct',                                  // {String} correct, incorrect, neutral
	latency: endTime,                                   // {Object} date end (optional)
	description: "Match some values and fill in a decimal."  // {String} question commonly
});

Numeric

SB.setInteraction({
	id: '8',                                            // {String}
	type: 'numeric',                                    // {String}
	objectives: [                                       // {Array}
		{                                               // {Object}
			id: '8_1'                                   // {String}
		}
	],
	timestamp: startTime,                               // {Object} date start
	correct_responses:  [                               // {Array}
		{                                               // {Object}
			pattern: {                                  // {Object} number range, only one allowed
  				min: 10,
				max: 11
			}
		}
	],
	weighting: '1',                                     // {String}
	learner_response: "10.5",                           // {String}
	result: 'correct',                                  // {String} correct, incorrect, neutral
	latency: endTime,                                   // {Object} date end (optional)
	description: "Fill in a random decimal like 10.5."  // {String} question commonly
});

LikeRT (Survey)

SB.setInteraction({
	id: '9',                                       // {String}
	type: 'likert',                                // {String}
	objectives: [                                  // {Array}
		{                                          // {Object}
			id: '9_1'                              // {String}
		}
	],
	timestamp: startTime,                          // {Object} date start
	correct_responses: [                           // {Array} ** Only supports 1 correct answer pattern
		{                                          // {Object}
			pattern: "strongly_agree"              // {String} SPM 10
		}
	],
	weighting: '1',                                // {String}
	learner_response: "strongly_agree",            // {String} a unique identifier for the group
	result: 'correct',                             // {String} correct, incorrect, neutral
	latency: endTime,                              // {Object} date end (optional)
	description: "Do you like to answer surveys?"  // {String} question commonly
});

Other

SB.setInteraction({
	id: '10',                                             // {String}
	type: 'other',                                        // {String}
	objectives: [                                         // {Array}
		{                                                 // {Object}
			id: '10_1'                                    // {String}
		}
	],
	timestamp: startTime,                                 // {Object} date start
	correct_responses: [
		{                                                 // {Object}
			pattern: "Anything we want."                  // {String}
		}
	],		
	weighting: '1',                                       // {String}
	learner_response: "Anything we want.",                // {String}
	result: 'correct',                                    // {String} correct, incorrect, neutral
	latency: endTime,                                     // {Object} date end (optional)
	description: "What is the 'other' interaction type?"  // {String} question commonly
});

Get Interaction

The response of this will generate the object highlighted above.

SB.getInteraction('id'); // id {String} returns {Object} or 'false'

Progress

Every time a objective is set this gets updated. Its actively calculating the cmi.score.scaled, progress_measure, completion_status and success_status. It will return back with a response object so if you have in your SCO Player that needs to reflect this information you can retrieve it.

Your Scoring/Progress goals may differ from the ones I present. You are completely open to adjust these to fit your needs. In my approach completion status and success status are completely independent. Meaning you could be complete, but fail or incomplete and pass.

SB.getProgress(); // returns {Object}

{
	score_scaled: '0',               // 0 - 1 as a decimal
	success_status: 'failed',        // or passed
	completion_status: 'incomplete', // or completed
	progress_measure: '0'            // 0 - 1 as a decimal
}

Commit

You may need to call commit between page turns. You may even have a interval in which you commit data to the LMS in the event of a network or power outage. When you do this, you'll need to call:

SB.commit();

Suspend

!! This is a terminating call !! This may be your default exit type. But if you have a navigational option to let them save for later, this would be the method you'd want to call.

SB.suspend();

Finish

!! This is a terminating call !! This may be your default exit type. But if you have a "Done" or "Submit" style button in your content you'd want to point it to this method.

SB.finish();

Timeout

!! This is a terminating call !! Timed session? This is the method you'd want to call to end it.

SB.timeout();
Clone this wiki locally