History and Advantages of Template-Based e-Learning Authoring

My last response on Responsive Web Design with Exam Engine and Training Studio got me to thinking about the advantages of templates in e-Learning. I first wrote on the subject in 1999 with the paper “Extended” Page Templates for Speeding Up CBT Development.  We later included it in 2001’s The ToolBook Companion. The idea behind that paper was the work we did in ToolBook to copy in “template pages” that are already configured rather than creating them by hand or haphazardly copying and pasting pages. We used this successfully in large custom training development efforts for companies like Baker-Hughes Inteq. Here is the dialog box for selecting a template:

ToolBookTemplateDialog

Here was my “advantages and disadvantages” statement:

Although there was quite a bit of up-front time to do this, we believe that it paid off nicely later in the project. The biggest advantages in the end were consistency and faster screen creation. Consistency refers to the fact that individual developers can’t introduce bugs on individual pages. They may expose bugs in the shared scripts or editors, but once these are fixed you know that all of the pages work correctly. This reduces the testing burden and allows the developers to focus on the content rather than the programming. The main disadvantage of the approach was the development time and skill needed to create a new extended page template. It wasn’t too bad for a page that was going to be used a number of times, but it was too much work for a unique page. Another disadvantage was more from a business perspective. Although the “Wellbore” approach worked very well for its specific project, it didn’t “scale up” that well to a general authoring solution. The shared scripts and editors were so optimized for efficient screen creation as to be of little use for developers outside of Platte Canyon. Since our focus is on products, we saw this as a disadvantage.

We had some thoughts of making a template-based ToolBook product, but the market looked questionable.

The next time templates came up was late 2002 when I ran an training class for the U.S. Army MANSCEN Schools based on my then new VBTrain.Net book. I quickly found out that their main interest was writing an editor to connect to an Access database. They had “storyboard” contractors that were entering content that was then used by the e-Learning developers. Rather than copying and pasting it, they wanted the content to read directly from the database. The rest of the weeklong class turned into me designing the database structure, writing the editor, and then writing the ToolBook side (templates and ADO code to populate the templates from the database). They were very happy with the solution as it boosted productivity considerably. But what invariably happened was that developers would edit the ToolBook files after populating them from the database. This caused the content to get out of sync with the database. Plus, the content had to published to DHTML each time a change was made.

In the meantime, we had created our .NET Question control and Exam Engine product (2003). The Army brought me back to create an ASP.NET solution that read the storyboard databases directly and dynamically brought in the images and media. Here is a screen shot:

ManscenScreen

This was a big step up, but the need for ASP.NET was problematic. Technically that kept the resulting training from being SCORM-compliant, since the SCORM package was supposed to be self-contained and an ASP.NET solution would not work on a non-Windows server or without a virtual directory being created.

We went back to the drawing board and created a Flash-based prototype using a more general database structure (similar to what we had created for Exam Engine). This product would become Training Studio in 2007, but before we could finish it, the Army awarded us a GSA contract in 2006 to create a complete set of editors and Flash templates for their same set of databases. Last we heard, they were still using this system.

As our Exam Engine also suffered from the disadvantages of being ASP.NET-based, we created a Silverlight version in early 2009 and a Flex/Flash version in late 2010. We quickly followed that up with a Flex version of Training Studio in 2010. Both products used our own question implementation (the original Training Studio used the Flash ActionScript 2 Question objects).

These products did fairly well, but like most Flash-based authoring tools, the writing was on the wall from the fact that that iPhone and iPad would not support Flash. So we rewrote both products from scratch, releasing version 4 of Exam Engine in May 2012 and version 3 of Training Studio in July 2012. Both of these were completely HTML/JavaScript/jQuery/CSS.

So why the big history lesson? Today we released version 4.5 of Exam Engine and 3.5 of Training Studio. The big change is the introduction of Responsive Web Design. It occurred to me that this ability was one more huge advantage of template-based authoring. So here is an overall list:

Advantages of Template-Based Authoring

  1. Content is independent of your display engine. We have Exam Engine customers who created their questions and media back in version 1 and have successfully updated it with minimal effort through these technologies: ASP.NET, Silverlight, Flash, and HTML. Since images and media are outside the authoring tool, they are easily updated without re-publishing your content. This is similar in concept to what Doc-To-Help offers for help authoring.
  2. e-Learning can be interactive yet still be created by non-programmers. Subject matter experts pick templates and fill in forms for the content. A “guru” can edit or create templates as needed.
  3. Localization is much easier since all content and images/media are stored externally and only brought together at runtime. The same editors used for content creation can also be used for localization.
  4. Pages are consistent. With traditional page-based authoring, authors tend to move titles and other items around. This can be distracting to the end user.
  5. Responsive Web Design is much easier. Imagine a training course of 5 lessons of 50 pages each. In most authoring tools, adjusting the content to the size of the browser is impossible. But even if it were possible, think of having to create CSS media queries for each unique page (potentially 250 pages in our example). That is such a huge job as to not being feasible. Instead, imagine that you are using templates. Even if you used all 30 Training Studio templates or 16 Exam Engine templates, creating/editing the CSS media queries is much more manageable.

For consistency, we should look at the disadvantages as well.

Disadvantages of Template-Based Authoring

  1. Not suited for lots of unique screen designs. To the extent that most screens are unique, creating templates to match can be a lot of work. Templates work best when you have consistent types of interactions such as image on the left and content on right, hotspots down the left side of the page, a video that fills the screen, etc.
  2. Different mindset. Authoring with templates can be frustrating to those authors who like to get it and “fiddle” with each page. As someone who has managed teams of e-Learning developers, I actually like keeping the authors out of the source as creative people tend to want to spend hours on a questionable animation rather than cranking out another five pages of training.
  3. Some technical expertise required. While template-based authoring is good for non-programmers, someone in the organization needs to have some HTML, JavaScript, and/or CSS experience in order to update and edit templates. Without this capability, a traditional authoring tool might be a better fit.

Thanks for sticking around for the history lesson!

Advertisements

SCORM and Closing the Browser Window: Flash/Flex

One of the new features of Tracker.Net 5 is the ability to show content in a frame instead of a popup window. One thing we noticed in testing our Exam Engine and Training Studio content is that we were assuming a popup window in that the Exit button closed the browser window and then that triggered the SCORM calls. With a frame, we need to send the SCORM calls first since the LMS will then close the frame. Since deployment via a popup window still requires sending SCORM messages when the user closes the browser window, we needed a global variable to distinguish the two situations.

The “Exit Button” code for Training Studio is shown below. We’ll leave the Exam Engine implementation for the next post since that has a Silverlight equivalent as well.

public function ImplementExitClick(e:MouseEvent):void {
    var exitMessage:String = ReadStructureSetting("ExitBtn_Message", "");
    var returnString:String;
    var okToExit:Boolean = true;

    if (exitMessage != "") {
        returnString = ExternalInterface.call("confirmClose", exitMessage);
        okToExit = ConvertToBoolean(returnString);
    }
    if (okToExit == true) {
        var exitSuccess:Boolean = ExitTraining();

        if (exitSuccess == true) {
            this.AlreadyExited = true;
            ExternalInterface.call("closeWindow");
        }
    }
}

We read our message (“Are you sure you want to exit?”) and display that to the user via JavaScript. If the user confirms, we call the ExitTraining method. This is where all the SCORM messages are sent. Assuming all went well, we set the AlreadyExited global variable to true and attempt to close the window (this fails silently if you are in a frame).

When we started the application, we registered the “onunload” browser event with the cleanup method as shown below. Notice that we check the same AlreadyExited variable before calling ExitTraining. This avoids it being called twice if the user clicks the Exit button to close the window.

// Added for exit handling
ExternalInterface.addCallback("exitTraining", cleanUp);

public function cleanUp():void {
    if (this.AlreadyExited == false) {
        ExitTraining();
        this.AlreadyExited = true;
    }
}

Creating an Adobe AIR Slide Show

The “Hack Ack” topic for the 2010 e-Learning Authoring Conference was Rock & Roll. I thought it would be fun to create a “Slide Show” application that would randomly grab all photos from a directory and display them. At the same time, it would randomly pick music files out of another directory, starting the next one as soon as the current one is finished. I decided to build my first incarnation in Flash/Flex (see the next post for a Windows Presentation Foundation example). But the security model of normal Flash is such that you cannot read the contents of directories on the user’s hard drive without user interaction. Adobe AIR applications don’t have that restriction, so I decided to tackle my first AIR application. The complete application can be downloaded as part of the Archives for Attendees, but we will look at some of the highlights.

The basic design is to read all the photos into one collection and all the music into another. We randomly grab a music file and start playing it. We handle its “completed” event and launch a new one when the music file is finished. We then start a timer to use with the photos. When the timer fires, we randomly pick a photo, remove it from the collection, and display the photo. To store information between timer cycles, we need to declare some variables outside a function block as shown below.

// configuration constant
private var photoDelay:int = 3000; // milliseconds

// shared variables
private var photoArrayCollection:ArrayCollection;
private var musicArrayCollection:ArrayCollection;
private var timerId:Timer;
private var soundChannelId:SoundChannel;

We use photoDelay in our timer. The two ArrayCollection variables allow us to store our list of photos and music respectively. The timerId variable stores the reference to our timer and the soundChannelId is the SoundChannel that we use to play our music.\

When the application starts, we call the configureAppfunction as shown below:

private function configureApp():void {
	// get list of media and photos
	var currentDirectory:File = File.applicationDirectory;
	var photoDirectory:File = currentDirectory.resolvePath("photos");
	var musicDirectory:File = currentDirectory.resolvePath("music");
	var photoArray:Array = photoDirectory.getDirectoryListing();
	var musicArray:Array = musicDirectory.getDirectoryListing();

	// use ArrayCollection so we can remove items easily after they are used
	photoArrayCollection = new ArrayCollection(photoArray);
	musicArrayCollection = new ArrayCollection(musicArray);
}

We use the AIR File class to find our two subdirectories (“photos” and “music”) and then get a listing of all the files in them. We store them in our associated photoArrayCollection and musicArrayCollection variables.

The code for the “Start” button is shown below:

protected function startBtn_clickHandler(event:MouseEvent):void
{
	// start timer and begin playing sound
	timerId = new Timer(photoDelay, photoArrayCollection.length); // once per photo
	timerId.addEventListener(TimerEvent.TIMER, timerHandler);
	timerId.start();

	playSound();		
}

We create our timer using our photoDelay variable (3000 milliseconds). The second parameter is the number of times that we want the timer to fire. We use the number of photos that we have. This is nice in that we don’t then have to stop the timer. The next line is where we tell Flash/Flex what function (timerHandler) to call when the timer fires. We then start the timer and call the playSoundfunction below.

private function playSound():void {
	if (musicArrayCollection.length > 0) {
		// get a random number between 0 and the length of the photoArrayCollection. 
		// Then show that photo and remove the item from the collection
		var rand:Number = Math.random();
		var indexNum:int = Math.round(rand * (musicArrayCollection.length - 1));
		var musicFileId:File = musicArrayCollection[indexNum] as File;
		var musicPath:String = musicFileId.nativePath;

		var soundId:Sound = new Sound();
		var requestId:URLRequest = new URLRequest(musicPath);

		soundId.load(requestId);
		if (soundChannelId != null) {
			soundChannelId.stop();
		}
		soundChannelId = soundId.play();
		soundChannelId.addEventListener(Event.SOUND_COMPLETE, soundCompletedHandler);	
		musicArrayCollection.removeItemAt(indexNum);				
		status = "Playing " + musicFileId.name + ". indexNum = " + indexNum + 
		    ". length = " + musicArrayCollection.length;
	}
}

private function soundCompletedHandler(e:Event):void {
	playSound();
}

We first check to make sure that we have music files to play. If so, we use the Math.random() function to get a random number between 0 and 1. We then multiply that by the number of sound files (we subtract 1 since we are creating an index that starts from 0). We then round the number so that we don’t have a decimal number. From there, we go to our musicArrayCollection and grab the associated sound file. We read its nativePath property to get its complete path. We create a Sound object and a URLRequest to actually read the file. We load sound and then check to see if a previous sound is playing. If so, we use the SoundChannel to stop it (that’s why we needed to save a reference to soundChannelId). We play the sound, which gives us the SoundChannel. We call the soundCompleted function when the music file finishes. Notice that this function just calls playSound again. Very importantly, we remove the file from the musicArrayCollection. This is how we avoid playing the file again and how we know to stop when all the files have been played. Finally, we set the status property, which causes the information on the name, index, and length to show up on the application’s status bar.
The last piece of the puzzle is displaying the photos. That happens in the timerHandler function shown below:

private function timerHandler(e:TimerEvent):void {
	var rand:Number = Math.random();
	var indexNum:int = Math.round(rand * (photoArrayCollection.length - 1));
	var photoFileId:File = photoArrayCollection[indexNum] as File;
	var photoPath:String = photoFileId.nativePath;

	photoImage.source = photoPath;	
	photoArrayCollection.removeItemAt(indexNum);	
	status = "Displaying " + photoFileId.name + ". indexNum = " + indexNum + 
        ". length = " + photoArrayCollection.length;
}

This logic is very similar to that used to play the music files. We again get a random number between 0 and the number of photos still available (-1 to account for the fact that we are starting at 0). We get the complete path and just set the source property of our Image control to that path. We remove the file from the collection and update our status bar.

If you would like to see how to implement this application in Windows Presentation Foundation and Visual Basic, see the next post.

Converting an ActionScript Value to Boolean

In a typical week, I’ll work with ActionScript, Visual Basic, OpenScript, JavaScript, and perhaps even InstallScript. One of the possible traps of moving between languages is making assumptions that similar functions in different languages will in fact operate the same way. One recent example that bit me was in working with a Boolean (true/false) value in Flex and ActionScript. The issue as in a simple confirmation as to whether it was OK to exit the lesson. The design was to call this JavaScript function in order to get a standard “confirmation” dialog from the browser:

confirmClose = function (message) {
    return window.confirm(message);
}

Here is how we called it from ActionScript:

var returnString:String = ExternalInterface.call("confirmClose", exitMessage);

So far, so good. But how do we convert the string returnString into a Boolean value? In other words, returnString will be either “true” or “false.” My mistake was to use the syntax:

var okToExit:Boolean = Boolean(returnString);

You would think this would work since the ActionScript Number() function will take a numeric string and convert it to a number. Similarly, the Visual Basic CBool() function will take “true” or “True” and convert it to the Boolean True. However, this “casting” function returns true for both “true” and “false!” So the user exited even if she clicked the “Cancel” button in the dialog. This is NOT what we wanted. If I had read the Flex documentation, this behavior is clearly stated:

Casting to Boolean from a String value returns false if the string is either null or an empty string (“”). Otherwise, it returns true.

So what was the fix? Easiest for me was to create a ConvertToBoolean function that operated like I expected. The new syntax plus the function is shown below.

var okToExit:Boolean = ConvertToBoolean(returnString);

public function ConvertToBoolean(inputVal:String):Boolean {
    var returnVal:Boolean;

    if (inputVal == "true") { // need to check string since Boolean() looks for 1 or 0
        returnVal = true;
    }
    else {
        returnVal = false; 
    }

    return returnVal;			
}

ActionScript Dictionaries

Question: In the context of Training Studio 2.0 templates, I am unclear on the full meaning of “dictionary.” Specifically, I am curious what this statement in the documentation means: The pageArrayLocal variable is a Dictionary that represents the current training page.

Answer: A Dictionary is actually a Flex/ActionScript object. It is similar to a Hashtable in Visual Basic or an associative array in JavaScript. The Dictionary has a key and a value. When we read the content.xml file in Training Studio, we put the content of each page into its own Dictionary.
The keys are column names and the values are the text that the subject matter put in. For example, here is a small section of the XML for a page:

<title>Training Studio TBCON Faculty Sample</title> 
<content_0>Welcome to the TBCON Faculty sample created in the Platte Canyon  Training Studio. To learn more about Training Studio, please visit ~external=http://www.trainingstudio.net~www.trainingstudio.net~.</content_0>        <
<graphic_0>trainingstudio.gif</graphic_0>


title, content_0, and graphic_0 are keys. “Training Studio TBCON Faculty Sample”, “Welcome…”, and “trainingstudio.gif” are values.
When Training Studio navigates to a page, it sets the pageArrayLocal variable to be the Dictionary associated with that page. We then look for the “templateType” key to figure out which template to use. Within the template, the loadData method loops through the keys and sets the appropriate content, reads in the media, load graphics, etc.

A related data type that you’ll run into is the ArrayCollection. It is similar to a Dictionary but does not have keys. Instead you refer to objects by their numeric position. In Training Studio, the TSCommon.masterContentArray is an ArrayCollection of all the Dictionaries (one per page) in the training.

See the next post for the equivalent objects in .NET.