An abstraction layer is a way of hiding complexities and maintaining cleanliness in your application. For example, if you want to save a file in your word processing application, you simply click “save”, while a whole host of actions is performed for you under-the-hood. In this example, you’re shielded from the complexity of your computer’s I/O operations by a graphical user interface.

When integrating tracking support (SCORM, AICC, etc,) into an an e-learning course, it’s a good idea to abstract as much of the tracking code as possible. For example, if you know your course is going to use SCORM 1.2, you might be tempted to write this:

//Assuming API is the SCORM API

//Display learner's name
var learner_name = API.LMSGetValue("cmi.core.student_name");

//Set course status to incomplete
API.LMSSetValue("cmi.core.lesson_status", "incomplete");

//Tell LMS to save whatever data we've sent
API.LMSCommit("");Code language: JavaScript (javascript)

This example hard-codes SCORM 1.2 into your course. If you ever decide to use SCORM 2004, you’d need to re-write whole chunks of your course. If you abstract the code by creating functions, you’re making it a little easier to update your code in the future:

//Assuming API is the SCORM API

function getLearnerName(){
   return API.LMSGetValue("cmi.core.student_name");
}

function setLessonIncomplete(){
   API.LMSSetValue("cmi.core.lesson_status", "incomplete");
}

function save(){
   API.LMSCommit("");
}

//Display learner's name
var learner_name = getLearnerName();

//Set course status to incomplete
setLessonIncomplete();

//Tell LMS to save whatever data we've sent
save();Code language: JavaScript (javascript)

Now that your tracking code is encapsulated in functions, it can be easily modified to include SCORM 2004 support:

//Assuming API is the SCORM API
//Checks for SCORM2004 mode, defaults to SCORM 1.2

function getLearnerName(){
   if(tracking_mode === "SCORM2004"){
      return API.GetValue("cmi.learner_name");
   }
   return API.LMSGetValue("cmi.core.student_name");
}

function setLessonIncomplete(){
   if(tracking_mode === "SCORM2004"){
      API.SetValue("cmi.completion_status", "incomplete");
   } else {
      API.LMSSetValue("cmi.core.lesson_status", "incomplete");
   }
}

function save(){
   if(tracking_mode === "SCORM2004"){
      API.Commit("");
   } else {
      API.LMSCommit("");
   }
}

//Set tracking mode
var tracking_mode = "SCORM2004";

//Display learner's name
var learner_name = getLearnerName();

//Set course status to incomplete
setLessonIncomplete();

//Tell LMS to save whatever data we've sent
save();Code language: JavaScript (javascript)

For e-learning courses, abstraction becomes even more powerful if you externalize your tracking-related functions. For example, you’d keep your tracking function invocations in your course code while placing the function definitions in a separate, easy-to-access file. This improves the separation of the tracking mechanism (SCORM, AICC, etc.) from the actual course logic.

Your course code:

//Set tracking mode
var tracking_mode = "SCORM2004";

//Display learner's name
var learner_name = getLearnerName();

//Set course status to incomplete
setLessonIncomplete();

//Tell LMS to save whatever data we've sent
save();Code language: JavaScript (javascript)

An external JavaScript file containing the function definitions:

//Assuming API is the SCORM API
//Checks for SCORM2004 mode, defaults to SCORM 1.2

function getLearnerName(){
   if(tracking_mode === "SCORM2004"){
      return API.GetValue("cmi.learner_name");
   }
   return API.LMSGetValue("cmi.core.student_name");
}

function setLessonIncomplete(){
   if(tracking_mode === "SCORM2004"){
      API.SetValue("cmi.completion_status", "incomplete");
   } else {
      API.LMSSetValue("cmi.core.lesson_status", "incomplete");
   }
}

function save(){
   if(tracking_mode === "SCORM2004"){
      API.Commit("");
   } else {
      API.LMSCommit("");
   }
}Code language: JavaScript (javascript)

Looking good, except for one thing… do you spot the big problem with this example? The tracking_mode variable is hard-coded in the course. A quick resolution is to dynamically set this variable via a querystring parameter when you load the course:

//Set tracking mode
//This is a quick and dirty example;
//there are better ways to handle querystrings
//Example: mycourse.html?tracking=SCORM2004
var tracking_mode = window.location.href.split("?tracking=")[1];

//Display learner's name
var learner_name = getLearnerName();

//Set course status to incomplete
setLessonIncomplete();

//Tell LMS to save whatever data we've sent
save();Code language: JavaScript (javascript)

At this point, your course code is abstracted enough that it would be (relatively) trivial to add AICC support by modifying your tracking functions.

As you can see, the further we abstract, the cleaner your course’s code becomes. If you’re a Flash developer, use an additional level of abstraction — place your tracking functions in JavaScript and invoke them via ExternalInterface. While it may add a little up-front work, it will enable you to easily update your tracking code without needing to republish your FLAs.

Your course’s JavaScript:

//Assuming API is the SCORM API
//Checks for SCORM2004 mode, defaults to SCORM 1.2

//Set tracking mode
//This is a quick and dirty example;
//there are better ways to handle querystrings
//Example: mycourse.html?tracking=SCORM2004
var tracking_mode = window.location.href.split("?tracking=")[1];

function getLearnerName(){
   if(tracking_mode === "SCORM2004"){
      return API.GetValue("cmi.learner_name");
   }
   return API.LMSGetValue("cmi.core.student_name");
}

function setLessonIncomplete(){
   if(tracking_mode === "SCORM2004"){
      API.SetValue("cmi.completion_status", "incomplete");
   } else {
      API.LMSSetValue("cmi.core.lesson_status", "incomplete");
   }
}

function save(){
   if(tracking_mode === "SCORM2004"){
      API.Commit("");
   } else {
      API.LMSCommit("");
   }
}Code language: JavaScript (javascript)

Your course’s FLA:

//Display learner's name
var learner_name:String = getLearnerName();

//Set course status to incomplete
setLessonIncomplete();

//Tell LMS to save whatever data we've sent
save();Code language: JavaScript (javascript)

And here’s the glue: an abstraction layer meant to bridge the course FLA and the JavaScript tracking. Save as an external AS file (or a class/package if you’re feeling adventurous).

import flash.external.*;

function getLearnerName():String {
   return ExternalInterface.call("getLearnerName");
}

function setLessonIncomplete():void {
   ExternalInterface.call("setLessonIncomplete");
}

function save(){
   ExternalInterface.call("save");
}Code language: JavaScript (javascript)

In this example the tracking mode is set via JavaScript, and all of the tracking functionality is handled via JavaScript. If you ever needed to change from SCORM 1.2 to SCORM 2004 or AICC, you could handle it all via your JavaScript file and leave your Flash untouched. Easy breezy.

These are very simple examples, but hopefully they show the power of abstraction. Notice the last two examples have zero mention of SCORM in the course code itself. Sweet!

Of course, there is a downside to everything. As I mentioned in a thread on the E-Learning Technology and Development Google Group, abstracting like this takes time and can add quite a bit of work to your course development plan. If you know your course is a one-off that will only ever use one tracking method (such as SCORM 1.2), abstraction layers may be more effort than they’re worth. But if you’re not in a hurry, or know that your course will be around for a long time and will run on multiple LMSs, abstraction is a very good idea.

Abstraction and the pipwerks SCORM API Wrappers

What about the pipwerks SCORM API Wrappers? Well, they use some abstraction, but are not as abstracted as these examples. They can certainly help you on your way, but would need a bit of tweaking to reach this level of abstraction. I’m currently working on new SCORM wrappers that aim to support this level of abstraction. Stay tuned.

Similar Posts