Skip to content

The Anti Library

Mark Statkus edited this page Feb 9, 2016 · 10 revisions

I often get asked...

Do I really need a library or framework for my content to communicate with SCORM?

This really depends on what your are doing. I often poke fun at 'powerpoint' SCO's that simply act as presentations, but normally go the extra extent of giving the student credit for visiting it. And you're right, there isn't much reason to lug around 30+ Kilobytes of infrastructure if you're only using <1% of it. That said, these type of presentations do have a valid place in learning. Also, there is a bit of history here. SCORM 1.2's Specification is almost completely optional. So really, the only base support you could rely on from 2001-2004 was a score, lesson status and a time value. This seemed to have a ripple effect on the remaining years up to today, due to the fact not every LMS adopted SCORM 2004, or adopted a partially implemented version of it.

Lets first talk about possible benefits from a library:

  1. You may already have a proven/tested system of executing content. Re-writing something custom could open yourself up to issues, but you can duplicate that consistency.
  2. You may be benefiting from a up or down conversion from SCORM 1.2 to 2004 or vice versa. You may even have a library that also support AICC. All these swiss army knife type approaches are hitting your bandwidth though.
  3. Looking for richer feedback, logging and get/set management all rolled up is handy. As you'll see below there is next to no error control vs. what you'd get with a library.
  4. For the features you are using, you don't want SCORM logic buried all over your content objects. Meaning, you have nested SCORM features/calls in certain parts of your content. Dev's may end up re-writing this logic if they didn't know it was already present.
  5. Leverage years of building content, on mixed platforms.

Bandwidth

If bandwidth is a concern for you, and all you are doing is loading, initializing, setting a score, completion, and success then committing and terminating, then no hauling around everything is a bit extreme. Specially with mobile devices and low bandwidth situations you'd want to maximize your approach. Now you may not of even known what the bare minimum even was, and actually its mostly just Initialize and Terminate. However, if you want to see a score, success and completion listed as well as possibly session time, you'll have to do just a little more work.

Couple solutions to lighten the load and take advantage of the users caching ability would be to point all your content to CDN's or a central media server. You gain a dependency on that domain/server being available, but it beats pushing the same file into all your SCO's. Sites like Cloudflare.com are great examples of free ways to host or work with already hosted scripts like http://cdnjs.com.

Example

Please note the following example is void of error management, and mostly assumes a connection. That could be tacked on, but for now its going to try to connect to the API_1484_11 (LMS) and then initialize. On exit, it will set the exit type, completion, success, score scaled, commit and terminate.

var API {
        connection: false,
        path: false,
        startTime: {}
    };

// FindAPI Method
function findAPI(win) {
    var attempts = 0, limit = 500;
    while ((!win.API && !win.API_1484_11) && (win.parent) && (win.parent !== win) && (attempts <= limit)) {
        attempts += 1;
        win = win.parent;
    }
    if (win.API_1484_11) {//SCORM 2004-specific API.
        API.version = "2004";
        //Set version
        API.path = win.API_1484_11;
    /*} else if (win.API) {//SCORM 1.2-specific API
        API.version = "1.2";
        //Set version
        API.path = win.API;*/
    } else {
        return false;
    }
    return true;
}

// LMS Connected Method
function lmsConnected() {
    API.path.Initialize("");
    API.startTime = new Date().getTime();
}

// centisecsToISODuration for SCORM 2004 (Needed for Session Time)
function centisecsToISODuration(n, bPrecise) {
    var str = "P",
        nCs = Math.max(n, 0),
        nY = 0,
        nM = 0,
        nD = 0,
        nH,
        nMin;
    // Next set of operations uses whole seconds
    //with (Math) { //argumentatively considered harmful
    nCs = Math.round(nCs);
    if (bPrecise === true) {
        nD = Math.floor(nCs / 8640000);
    } else {
        nY = Math.floor(nCs / 3155760000);
        nCs -= nY * 3155760000;
        nM = Math.floor(nCs / 262980000);
        nCs -= nM * 262980000;
        nD = Math.floor(nCs / 8640000);
    }
    nCs -= nD * 8640000;
    nH = Math.floor(nCs / 360000);
    nCs -= nH * 360000;
    nMin = Math.floor(nCs / 6000);
    nCs -= nMin * 6000;
    //}
    // Now we can construct string
    if (nY > 0) {
        str += nY + "Y";
    }
    if (nM > 0) {
        str += nM + "M";
    }
    if (nD > 0) {
        str += nD + "D";
    }
    if ((nH > 0) || (nMin > 0) || (nCs > 0)) {
        str += "T";
        if (nH > 0) {
            str += nH + "H";
        }
        if (nMin > 0) {
            str += nMin + "M";
        }
        if (nCs > 0) {
            str += (nCs / 100) + "S";
        }
    }
    if (str === "P") {
        str = "PT0H0M0S";
    }
    // technically PT0S should do but SCORM test suite assumes longer form.
    return str;
}
// initSCO Method
function initSCO() {
    // Search for LMS API
    var win;
    try {
        win = window.parent;
        if (win && win !== window) {
            findAPI(window.parent);
        }
    } catch (e) {/* Cross Domain issue */
    }
    if (!API.path) {
        try {
            win = window.top.opener;
            findAPI(win);
        } catch (ee) {/* Cross domain issue */}
    }
    if (API.path) {
        API.connection = true;
        lmsConnected();
        return true;
    }
    // I was unable to locate an API for communication;
}

// Exit SCO Method
function exitSCO() {
    var session_secs;
    API.endTime = new Date().getTime();
    API.path.SetValue('cmi.exit', 'normal');
    API.path.SetValue('cmi.completion_status', "completed");
    API.path.SetValue('cmi.success_status', 'passed');
    API.path.SetValue('cmi.score.scaled', '1');
    // Could save session time, but it would take a little more code.
    session_secs = (API.endTime - API.startTime) / 1000;
    API.path.SetValue('cmi.session_time', centisecsToISODuration(session_secs * 100, true));
    API.path.Commit("");
    API.path.Terminate("");
}

// Events for Load/Unload
$(window).bind('load', initSCO);
$(window).bind('unload', exitSCO);