This entry is part 6 of 9 in the series SCORM for Developers

In the last lesson in this SCORM for Developers series, we dipped our toes in the water and created the most barebones SCORM course possible. In this lesson, we’ll wade a little deeper, adding sophistication to the course via a smattering of JavaScript and HTML. We’ll use cmi.core.lesson_status, implement some error-checking, personalize the content, and even require an interaction before granting a course completion.

Don’t be alarmed by the fast pace of this lesson — this is just a quick primer covering the basic concepts and architecture. Remember, this is a non-functioning example. We will walk through fully functional examples line-by-line later in the series.

Checking Lesson Status

The previous lesson included the CMI field cmi.core.lesson_status. This field is responsible for maintaining the status of the course. It’s a read/write field, which means you can retrieve its value, and you can also change its value.

cmi.core.lesson_status is not a free-form text field, and will only accept specific values (referred to as tokens in the SCORM documentation). The allowed values for cmi.core.lesson_status are:

  • “not attempted” — the default status.
  • “incomplete” — the course has been started but the learner has not finished it.
  • “completed” — indicates course is completed but does not indicate whether the appropriate passing score was reached.
  • “passed” — indicates course is both completed and passed.
  • “failed” — the opposite of “passed”, the course is likely completed but the learner hasn’t met the criteria for passing (the criteria is defined by you, within your own course logic, not by SCORM).
  • “browsed” — the course has been launched in “browse mode”.
    • I don’t recommend using browse mode, as I find it often creates confusion regarding completion status and score. There are other techniques available for enabling a learner to relaunch a course after completion without altering the score, which will be covered later in this series.

The statuses you’ll use most often are “incomplete” coupled with either “completed” or “passed”, depending on your preference and needs. You might use “failed”, but who are we kidding, most courses are built to never let the learner fail! (But by all means, use it if it fits your scenario.)

It’s a best practice to check the course status immediately upon launching your course. This will enable you to properly handle several scenarios:

  1. If this is the first time the learner has launched the course, the value will typically be “not attempted”. In most cases, you will want to immediately change the value to “incomplete”, which officially denotes (implies) the learner has accessed the course at least once.
  2. If the course status is set to “incomplete”, in most cases this is where you would insert some code to check for a bookmark, so you can redirect the learner to where they left off when they exited the course.
  3. If the course status is set to “completed” or “passed”, you can assume the learner is back to review content. In this scenario, you’d disable tracking in your course, freeing the learner to navigate and interact with the course without fear of altering the course status (and score, if applicable) achieved when they initially completed the course.

For the purposes of our little example, let’s keep it simple and check to see if the course has already been set to “completed” or “passed” before we try to change the value of cmi.core.lesson_status. We can do this by using LMSGetValue, which, as you may have guessed, retrieves the value of a CMI field from the LMS.

<!DOCTYPE html>
<html>
<head>
   <meta charset="UTF-8">
   <title>SCORM Course</title>
</head>
<body>

<script>

var API = window.API;

//begin the SCORM session
API.LMSInitialize("");

//Get the lesson status from LMS
var lesson_status = API.LMSGetValue("cmi.core.lesson_status");

//Only set the status to "completed" if it is not already considered completed
if(lesson_status !== "completed" && lesson_status !== "passed"){

   //Explicitly set course status to “completed”
   API.LMSSetValue("cmi.core.lesson_status", "completed");

   //Don’t forget to commit!
   API.LMSCommit("");

}

//end the SCORM session
API.LMSFinish(""); 

</script>
</body>
</html>
Code language: HTML, XML (xml)

In this example, the conditional statement will only set the course to “completed” if it hasn’t already been set to “completed” or “passed”. If the course has been completed, don’t try to set the course to “completed” again, just leave it be.

Error Checking

Some will say it’s a good practice to include error-checking. I say it’s imperative. Case in point: What happens when LMSInitialize isn’t successful? (It’s rare, but it happens.)

If LMSInitialize is not successful, and you don’t account for it in your course logic, the learner will continue to interact with the course, thinking the course is functioning normally, but the LMS will not be saving any information about the course session. In our little example, the learner would only miss out on the completion status, but in the real world, a learner could navigate through dozens of course pages and not receive credit. You’d spend the next week answering angry support calls!

Thankfully, LMSInitialize returns a value indicating whether initialization was successful.

As seen in the code example below, we can perform a check to ensure LMSInitialize is successful before moving on to our other course functions. If LMSInitialize is not successful, we can alert the user.

<!DOCTYPE html>
<html>
<head>
   <meta charset="UTF-8">
   <title>SCORM Course</title>
</head>
<body>

<script>

var API = window.API;

//begin the SCORM session
var isInitialized = (API.LMSInitialize("") === "true"); //create a boolean value

if(isInitialized){

   //Get the lesson status from LMS
   var lesson_status = API.LMSGetValue("cmi.core.lesson_status");

   //Only set the status to "completed" if it is not already considered completed
   if(lesson_status !== "completed" && lesson_status !== "passed"){

      //Explicitly set course status to “completed”
      API.LMSSetValue("cmi.core.lesson_status", "completed");

      //Don’t forget to commit!
      API.LMSCommit("");

   }

   //end the SCORM session
   API.LMSFinish(""); 

} else {

   alert("Uh-oh, LMSInitialize failed!");

}

</script>
</body>
</html>
Code language: HTML, XML (xml)

The course now validates the initialization, and alerts the learner if the initialization failed.

As you can see, error-checking doesn’t have to be difficult, but it’s very important, both for reliability of your course and for user satisfaction. Many of the older SCORM tutorials on the interwebs don’t utilize error-checking, which I feel is a big mistake. We will continue to implement error-checking strategies throughout the examples in this series.

Notes

  • In yet another quirk of the SCORM API, LMSInitialize returns a ‘string boolean.’ Instead of an authentic boolean (true or false), we get the stringified versions (“true” and “false”). We need to account for these in our script.
  • Some LMSs don’t return “true” or “false” as specified by the SCORM spec. This is one instance where SCORM wrappers come in handy. SCORM wrappers will be covered a bit later.

Personalization

Another nice touch we can add is personalization. The CMI field cmi.core.student_name provides the learner’s name, which we can dynamically insert into the course. Building on our example code, we can add a small bit of HTML markup, and a touch of JavaScript to populate it with the learner’s name.

<!DOCTYPE html>
<html>
<head>
   <meta charset="UTF-8">
   <title>SCORM Course</title>
</head>
<body>

<p>Hello, <span id="learner_name"></span>.</p>

<script>

var API = window.API;

//Grab a reference to the learner_name span for future use
var name_span = document.querySelector("#learner_name");

//begin the SCORM session
var isInitialized = (API.LMSInitialize("") === "true"); //create a boolean value

if(isInitialized){

   //Get the learner's name from the LMS
   var learner_name = API.LMSGetValue("cmi.core.student_name");

   //Populate the learner_name span element with the learner's name
   name_span.innerHTML = learner_name;

   //Get the lesson status from LMS
   var lesson_status = API.LMSGetValue("cmi.core.lesson_status");

   //Only set the status to "completed" if it is not already considered completed
   if(lesson_status !== "completed" && lesson_status !== "passed"){

      //Explicitly set course status to “completed”
      API.LMSSetValue("cmi.core.lesson_status", "completed");

      //Don’t forget to commit!
      API.LMSCommit("");

   }

   //end the SCORM session
   API.LMSFinish(""); 

} else {

   alert("Uh-oh, LMSInitialize failed!");

}

</script>
</body>
</html>
Code language: HTML, XML (xml)

It may seem like a minor improvement, but in my opinion, including your learner’s name on the screen helps improve confidence in the course. It also provides the opportunity to personalize any interactive sections you may build within the course. For example, you can create prompts for interactions such as “OK, Jane, are you ready to give it a try?”

Notes

  • When sending cmi.core.student_name, LMSs usually include both first name (given name) and last name (surname). There is no official standard for whether the name is returned in a specific sequence, such as “Jane Doe” or “Doe, Jane”, so be careful how you choose to use the cmi.core.student_name field.

Require User Interaction

As currently constructed, the course doesn’t require any interaction from the learner to earn their course completion. Let’s require the learner to do something before granting the completion. For simplicity’s sake, we’ll just require a button click. To do this, we add a button to the HTML, then add a bit of JavaScript to handle what happens when the button is clicked.

<!DOCTYPE html>
<html>
<head>
   <meta charset="UTF-8">
   <title>SCORM Course</title>
</head>
<body>

<p>Hello, <span id="learner_name"></span>.</p>
<button id="btn_complete">Click me to complete this course</button>

<script>

var API = window.API;

//Grab a reference to the learner_name span for future use
var name_span = document.querySelector("#learner_name");

//Grab a reference to the button for future use
var btn = document.querySelector("#btn_complete");

//Set up a click handler for the button
btn.addEventListener("click", function(e){
   //Ensure the browser only does what we tell it to when the button is clicked
   e.preventDefault();
});

//begin the SCORM session
var isInitialized = (API.LMSInitialize("") === "true"); //create a boolean value

if(isInitialized){

   //Get the learner's name from the LMS
   var learner_name = API.LMSGetValue("cmi.core.student_name");

   //Populate the learner_name span element with the learner's name
   name_span.innerHTML = learner_name;

   //Get the lesson status from LMS
   var lesson_status = API.LMSGetValue("cmi.core.lesson_status");

   //Only set the status to "completed" if it is not already considered completed
   if(lesson_status !== "completed" && lesson_status !== "passed"){

      //Explicitly set course status to “completed”
      API.LMSSetValue("cmi.core.lesson_status", "completed");

      //Don’t forget to commit!
      API.LMSCommit("");

   }

   //end the SCORM session
   API.LMSFinish(""); 

} else {

   alert("Uh-oh, LMSInitialize failed!");

}

</script>
</body>
</html>
Code language: HTML, XML (xml)

In this updated code, we’ve added the button to click, and a click handler, but the click handler doesn’t actually do anything yet.

This button’s purpose is to set the course to “completed” when clicked, so let’s cut and paste the relevant parts of the lesson_status check into the click handler:

<!DOCTYPE html>
<html>
<head>
   <meta charset="UTF-8">
   <title>SCORM Course</title>
</head>
<body>

<button id="btn_complete">Click me to complete this course</button>

<script>

var API = window.API;
var btn = document.querySelector("#btn_complete");

btn.addEventListener("click", function(e){

   //Ensure the browser only does what we tell it to when the button is clicked
   e.preventDefault(); 

   if(isInitialized){

      //Get the lesson status from LMS
      var lesson_status = API.LMSGetValue("cmi.core.lesson_status");

      //Only set the status to "completed" if it is not already considered completed
      if(lesson_status !== "completed" && lesson_status !== "passed"){

         //Explicitly set course status to “completed”
         API.LMSSetValue("cmi.core.lesson_status", "completed");

         //Don’t forget to commit!
         API.LMSCommit("");

      }

      //end the SCORM session
      API.LMSFinish(""); 

   } else {

      alert("Can't set the course to complete: it hasn't been initialized");

   }

});

//begin the SCORM session
var isInitialized = (API.LMSInitialize("") === "true"); //create a boolean value

if(isInitialized){

   //do nothing

} else {

   alert("Uh-oh, LMSInitialize failed!");

}

</script>
</body>
</html>
Code language: HTML, XML (xml)

Note I’ve wrapped the completion code in an if() statement, ensuring it will only be invoked if the course was properly initialized.

As currently constructed, when the course is launched, it will initialize the SCORM connection, but won’t do anything else. When the learner clicks the button, the course will be set to “completed” and the SCORM connection will be terminated.

So far, so good. But let’s improve the user experience by providing textual feedback to the learner when the button is clicked.

First we need to provide a place to put the text. In the <body>, we’ll add a heading, and some instruction for the learner. Then we’ll dynamically change the text when the button is clicked.

<!DOCTYPE html>
<html>
<head>
   <meta charset="UTF-8">
   <title>SCORM Course</title>
</head>
<body>

<h1>Course Title</h1>

<p>Hello, <span id="learner_name"></span>.</p>

<div id="message">
Please click the button to indicate you have completed this course.
</div>

<button id="btn_complete">Click me to complete this course</button>

<script>

var API = window.API;

//Grab a reference to the learner_name span for future use
var name_span = document.querySelector("#learner_name");

//Grab a reference to the button for future use
var btn = document.querySelector("#btn_complete");

//Grab a reference to the 'message' div for future use
var message_div = document.querySelector("#message");

//Set up a click handler for the button
btn.addEventListener("click", function(e){

   //Ensure the browser only does what we tell it to when the button is clicked
   e.preventDefault();

   if(isInitialized){

      //Get the lesson status from LMS
      var lesson_status = API.LMSGetValue("cmi.core.lesson_status");

      //Only set the status to "completed" if it is not already considered completed
      if(lesson_status !== "completed" && lesson_status !== "passed"){

         //Explicitly set course status to “completed”
         API.LMSSetValue("cmi.core.lesson_status", "completed");

         message_div.innerHTML = "Course set to completed. Congratulations!";

         //Don’t forget to commit!
         API.LMSCommit("");

      } else {

         message_div.innerHTML = "Course has already been completed.";

      }

      //end the SCORM session
      API.LMSFinish(""); 

   } else {

      alert("Can't set the course to complete: it hasn't been initialized");

   }

});

//begin the SCORM session
var isInitialized = (API.LMSInitialize("") === "true"); //create a boolean value

if(isInitialized){

   //Get the learner's name from the LMS
   var learner_name = API.LMSGetValue("cmi.core.student_name");

   //Populate the learner_name span element with the learner's name
   name_span.innerHTML = learner_name;

} else {

   alert("Uh-oh, LMSInitialize failed!");

}

</script>
</body>
</html>
Code language: HTML, XML (xml)

The value of <div id=”message”> changes based on the outcome of the button click. It sounds so simple, but is a very powerful way to improve the user experience.

With this change, the learner gets feedback as soon as the button is clicked. If the course is properly initialized, and the course has not already been completed, the learner is informed the course is now complete. If the initialization failed, the learner is informed of the failure. If the course has already been completed, the learner is informed of the prior completion.

Let’s take a moment to further improve the user experience. Now that we’ve established an on-screen location for providing feedback to the learner, we can replace all of the alert() calls with on-screen text. This is much less annoying than a parade of pop-up alerts. We can also hide the button after it has been clicked, since it’s no longer needed and we don’t want the learner to continue to click it.

<!DOCTYPE html>
<html>
<head>
   <meta charset="UTF-8">
   <title>SCORM Course</title>
</head>
<body>

<h1>Course Title</h1>

<p>Hello, <span id="learner_name"></span>.</p>

<div id="message">
Please click the button to indicate you have completed this course.
</div>

<button id="btn_complete">Click me to complete this course</button>

<script>

var API = window.API;

//Grab a reference to the learner_name span for future use
var name_span = document.querySelector("#learner_name");

//Grab a reference to the button for future use
var btn = document.querySelector("#btn_complete");

//Grab a reference to the 'message' div for future use
var message_div = document.querySelector("#message");

//Set up a click handler for the button
btn.addEventListener("click", function(e){

   //Ensure the browser only does what we tell it to when the button is clicked
   e.preventDefault();

   if(isInitialized){

      //Get the lesson status from LMS
      var lesson_status = API.LMSGetValue("cmi.core.lesson_status");

      //Only set the status to "completed" if it is not already considered completed
      if(lesson_status !== "completed" && lesson_status !== "passed"){

         //Explicitly set course status to “completed”
         API.LMSSetValue("cmi.core.lesson_status", "completed");

         message_div.innerHTML = "Course set to completed. Congratulations!";

         //Don’t forget to commit!
         API.LMSCommit("");

      } else {

         message_div.innerHTML = "Course has already been completed.";

      }

      //end the SCORM session
      API.LMSFinish(""); 

   } else {

      message_div.innerHTML = "Can't set the course to complete: it hasn't been initialized";

   }

   //hide the button so it can't be clicked anymore
   btn.style = "display:none;";

});

//begin the SCORM session
var isInitialized = (API.LMSInitialize("") === "true"); //create a boolean value

if(isInitialized){

   //Get the learner's name from the LMS
   var learner_name = API.LMSGetValue("cmi.core.student_name");

   //Populate the learner_name span element with the learner's name
   name_span.innerHTML = learner_name;

} else {

   message_div.innerHTML = "Uh-oh, LMSInitialize failed!";

}

</script>
</body>
</html>
Code language: HTML, XML (xml)

Three lines of JavaScript can make a big difference for the user experience.

Lesson Wrap-Up

And there you have it: a simple, usable SCORM course.

Again, we’re using a fake API connection (window.API) for simplicity’s sake, so this example will not function in a real LMS yet.

Granted, this example is terribly contrived and would not be very useful (or fun) in a real-world environment, but everything except the API connection is fully-functioning and valid.

We breezed through this exercise rather quickly, but I hope you get the gist of the lesson: SCORM code is just a tiny bit of JavaScript and nothing to be afraid of. In fact, I’ll argue that the SCORM code within most e-learning courses is usually the smallest component of the codebase — the course’s presentation and navigation scripts are usually the most complex pieces of the puzzle.

Check the next few installments of this series for examples that will run in real-world LMS. These examples will utilize open-source JavaScript libraries to handle the presentation and navigation features, and we’ll bolt on the SCORM code to make them LMS-ready.

Later in the series, when we’ve established more of a comfort level with SCORM code and course-building concepts (bookmarking, scoring, etc.), we will build a simple-yet-complete HTML course from scratch.

Series Navigation<< A Simple SCORM ExampleTesting SCORM Courses >>

Similar Posts