Making a jQueryUI Button

We now use the nice jQueryUI library in Tracker.Net, Exam Engine, and Training Studio. In each case, you link in the correct JavaScript files and style sheet and then use JavaScript to create the button. The nice thing is that you can use various types of HTML objects (input, anchor, ASP.NET buttons, etc.) and they still end up looking like a button. Here is an example from Tracker.Net:

$(function () {
        var optionsBtnId = $("#optionsBtn");

        if (optionsBtnId.length) {
            optionsBtnId.button({
                icons: {
                    primary: "ui-icon-home"
                },
                text: false
            });
            optionsBtnId.click(function () {
                var returnVal = true;

                if (isOnCoursePage == true) {
                    if (isOneCourse == true) {
                        window.resizeTo(800, 600);
                    }
                    else {
                        returnVal = checkIfOkToLeave(LessonOpenMessageLeavingScreen);
                    }
                }

                return returnVal;
            });
        }
    });

The $() means that the jQuery will call the function after the page fully loads. We then get our hands on the HTML object that we want to use for our button. The #optionsBtn means that we are looking for an object with an id of “optionsBtn.” We then call the jQueryUI button method to create the button. In this case, we tell it to use the jQueryUI home icon and not to have any text. Next, we define the click function. Here we are doing some logic to determine whether to proceed with the click. If returnVal is false, the server side event will not be processed and we stay on the page.

Advertisements

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!

Responsive Web Design with Exam Engine and Training Studio

Responsive Web Design is a fairly new concept with the idea being that we want our web content to adjust to different browser capabilities and, in particular, viewport or screen sizes. So rather redirecting to a different set of content when accesses by an iPhone, iPad, or Android device with a smaller screen, we adjust our content accordingly. Ethan Marcotte coined the phrase and is one of its biggest proponents. Here are two articles that we found helpful: http://alistapart.com/article/responsive-web-design and http://unstoppablerobotninja.com/entry/fluid-images. We first learned about the concept in this excellent book: http://www.amazon.com/gp/product/B007SVJA3M/ref=oh_d__o00_details_o00__i00.

Making Exam Engine and Training Studio content responsive is challenging in the sense that you have to deal with both the background (index.htm) and the templates shown within the iFrame. But having templates rather than individual pages of training makes it quite a bit easier as there are a limited number of templates to fine-tune. The approach we used is to adjust the background elements both horizontally and vertically so they fit with both small and large screen sizes. With the templates, we only adjust them horizontally. This keeps the templates from running over the navigation buttons and so forth when they are resized[1]. Here is a matching template from Exam Engine at full size:

matching

Here it is again at a width of about 520 pixels:

matching500

Notice that we moved the Previous and Next buttons
to the left of the screen. The status text (not shown) is now below those
buttons and above the audio controls (which the items within the template have
sized automatically so they still fit on the screen). The drag and drop
functionality still works fine, though with this particular template the
“reset” functionality only operates correctly
when the user has not resized the screen while this template is
displayed.

Here is the same screen at about 390 pixels. It
still looks good but any smaller and we start running into problems with the
support text wrapping into the question text and so forth. Notice that we have
moved the exam name to its own line so that it doesn’t overlap the “Question 10
of 10” text. We have also moved the “countdown timer” right next to the Score
Exam button so that it doesn’t get cut off.

matching400

Here is an even smaller screen size (290 pixels). We have moved the support text to its own line[2]. It now wraps and gets in the way of the question text. But some minor adjustments on the length of the text or omitting the support link altogether would take care of that. Long feedback and status text also causes problems at such a small resolution. But even without these adjustments, we’ve gone a long way towards making our content quite viewable at multiple screen sizes.

matching300

The general approach to making this work is to first leave the style sheets as described above alone until we get to a width below our default size (800 pixel width for the templates that come with Exam Engine). We then use a CSS media query to adjust from there. It is important to understand that these CSS settings then stay in place until either another media query kicks in (such as at 400 pixels or whatever) or if the user resizes her browser above the value[3].

backgroundstyles.css

There are multiple media queries. At the first one (800 pixels width), we change all left and width values to the corresponding percentage values. As discussed in the references above, this uses the all-important formula: result = target / context. In our case, the context is our width, typically 800 pixels. The target is the existing amount in pixels. So if we have left: 8px;, we change it to left: 1%;, since 8 / 800 = 1%. Here are the media queries with some comments in line.

@media screen and (max-width: 800px)
{
	#userNameLabel
	{
	     left: 2.5%; /* 20px (target) / 800px (content) = 0.025 - 2.5%*/
	}

	#numQuestionsLabel
	{
	     left: 31.25%;
	}

	#examNameLabel
	{
	     left: 53.75%;
	}

	/* rest omitted for space reasons */
}

@media screen and (max-width: 768px)
{
	/* We switch the left and width of the status label and the various buttons to a fixed pixel amount so that they won’t move further or get too thin. */

	#statusLabel   
	{ 
	     width: 235px; 
	}   

	#previousBtn 
	{ 
	     left: 250px; 
	}   

	#emailResultsBtn 
	{ 
	     left: 310px; 
	}   

	#previousImageBtn, #emailResultsImageBtn 
	{ 
	     left: 318px; 
	}  

	/* rest omitted for space reasons */

}

The templatestyles.css and the individual style sheets for the templates have similar media queries. Of particular interest is the handling of images and media that are designed to fill a particular portion of the screen. Here is the media query from hotobjects.css.

@media screen and (max-width: 791px)
{
	#answer_1, #answer_2, #answer_3, #answer_4, #answer_5, #answer_6, #answer_7, #answer_8
	{
		max-width: 23.75%;
	}

	#answer_2
	{
		left: 23.75%;
	}

	#answer_4
	{
		left: 23.75%;
	}

	#answer_5
	{
		left: 47.5%;
	}

	#answer_6
	{
		left: 47.5%;
	}

	#answer_7
	{
		left: 71.25%;
	}

	#answer_8
	{
		left: 71.25%;
	}

	#instructionsFeedbackText
	{
		width: 55.375%;
	}
}

We use the max-width setting to ensure that the images (answer_1, answer_2, etc.) scale down as we reduce the screen width. On an 800 pixel width screen, the images are designed to be a maximum of 190 pixels wide. So we use our result = target / context formula to come up with 23.75% = 190 / 800. Note that this query starts at 791 pixels rather than 800. That is because the iFrame itself is 792 pixels wide and testing revealed that we needed a 1-pixel offset.


[1] Another way the media query could go away or change to a different value is if the user changes the orientation of the device, such as going from portrait to landscape.


[2] All of these screen shots have a spot for the student name, which accounts for the space at the top of the screen.


[3] This can still happen if you have long feedback or lots of text. If you are deploying to smaller devices like iPhones, we recommend either limiting question feedback or adjusting backgroundstyles.css to further push down the navigation buttons.

Enabling the Next Page Button after Media Completion in Training Studio

One of our Training Studio customers wanted to disable the “Next Page” button on a “Media Full Screen” template until the video was completed. We had the hooks in there and thus this was an easy change. The relevant “load” code is shown below.

$(function () {
	// template population code omitted

	var videoId = document.getElementById("player_0");

	if (videoId != null) { // can play HTML5 video
	    videoId.addEventListener("ended", contentCompleted);
	}
	else {
		contentCompleted();
	}
	// bind keyboard events
	$(document).keydown(parent.ImplementKeyDown);
});

The $() means that the jQuery will call the function after the page fully loads. We then get our hands on the HTML object that we use to play our HTML 5 audio or video. If the browser is not HTML 5 capable, then we call the contentCompleted function right away so that the user is not stuck. We then handle the ended event with the addEventListener method. We tell it to call the contentCompleted function when the video is ended. This function is shown below.

function contentCompleted(e) {
    // show completion image
    parent.CompletionImageRef.show();
    //enable next page button
    parent.SetBtnStatus(parent.NextButtonRef,"Next",true,true);
}

We make two function calls to our main JavaScript file. We refer to it via parent since it is attached to the parent object (the templates are shown in an iFrame of the page – so the page is the parent). The CompletionImageRef.show() line shows our completion image while the SetBtnStatus method sets the Next page button to enabled (and keeps it visible).

JavaScript and jQuery in Training Studio

The new version of Training Studio is completely HTML and JavaScript. Rather than using ActionScript to create interactions as in past versions, we now use JavaScript. In particular, we take advantage of the jQuery library to make it even easier than in past versions. Let’s look at an example. The script below is the entire JavaScript for the buttonClickLeftShowContentImageMediatemplate. The interaction is “buttonClick” on hotspots. In response to the click, the template shows content (text), an associated image, and/or plays associated media. I will make some comments/explanations in-line.

var lastHotspot = null;
var completionArray = [];
var numHotspots = 0;

Since these variables are declared outside a function block, they are effectively global to the page. They are used to keep track of completion and the “last” hotspot accessed (in order to set its style to HotspotCompleted).

$(function () {
	var pageArrayLocal = parent.pageArray; // associative array
	var keyId;
	var keyString;
	var keyValue;
	var objectSelector;
	var objectId;
	var hotspotNum;

The $ refers to jQuery. This function is called once the page completely loads. The reference to parent is to the index.htm page. Since it references TrainingStudioSupport.js, the parent.pageArray means that we are reading the global variable from that file. This pageArrayLocal variable is basically a dictionary that represents the current training page. The key matches up to the node or column in the database. They are title, subtitle, content_0, etc.

	parent.ShowTransition();

As with the pageArray variable, the line above calls the showTransition function in TrainingStudioSupport.js. This just shows the iFrame holding the template.

	for (keyId in pageArrayLocal) {
		keyString = keyId.toString();
		keyValue = pageArrayLocal[keyId];

		// handle unique ones here. Handle the rest in PopulateTemplate (TrainingStudioSupport.js)

We loop through each of the keys (content_0, media_0, etc.) and only handle the ones that need special handling by this template.

		switch (keyString) {
			// let "media_0" get handled by template       
			case "media_1":
			case "media_2":
			case "media_3":
			case "media_4":
			case "media_5":
			case "media_6":
			case "media_7":
			case "media_8":
			case "media_9":
			case "media_10":
				// let graphic_0 be handled by template   
			case "graphic_1":
			case "graphic_2":
			case "graphic_3":
			case "graphic_4":
			case "graphic_5":
			case "graphic_6":
			case "graphic_7":
			case "graphic_8":
			case "graphic_9":
			case "graphic_10":
				break;

We let standard PopulateTemplate (see below) method handle media_0 and graphic_0, since we want any initial sound, video, or animation to play and any initial graphic to display. The rest of the media (media_1 – media_10) and graphics (graphic_0 – graphic_10are only played/displayed in response to the hotspot interaction. So we don’t do anything except break when we encounter them.

			case "hotspot_1":
			case "hotspot_2":
			case "hotspot_3":
			case "hotspot_4":
			case "hotspot_5":
			case "hotspot_6":
			case "hotspot_7":
			case "hotspot_8":
			case "hotspot_9":
			case "hotspot_10":
				var contentId = parent.formatHotspot(keyValue);

				objectName = "#" + keyString;
				objectId = $(objectName);

				hotspotNum = parent.getFieldNum(keyString);
				numHotspots = Math.max(hotspotNum, numHotspots);
				objectId.html(contentId);
				objectId.show();
				objectId.click(hotspotClickHandler);

				break;

We first build a reference to the associated span object using jQuery. A jQuery reference to an object with an id of “hotspot_1” looks like this: $(“#hotspot_1”); In jQuery terms, the “#hotspot_1” is the selector. We call the formatHotspot() method of the TrainingStudioSupport.js using parent once again. This method looks for special bullet and hyperlink characters and returns the proper HTML. After creating our jQuery object reference, we strip the number (1, 2, 3, etc.) from the name of the object and use it to populate our hotspotNum variable. We keep a running total of the numHotspots as well. We use this to determine when all the hotspots have been selected. Finally, we associate the control’s click event with our hotspotClickHandlerfunction. This is what makes something happen when the user clicks on the hotspot.

			default:
				objectName = "#" + keyString;
				objectId = $(objectName);
				parent.PopulateTemplate(objectId, keyString, keyValue);

This line is where all the keys that we didn’t specifically handle are sent to PopulateTemplateinstead. This avoids duplicate code in every template.

		}
	}
	// bind keyboard events
	$(document).keydown(parent.ImplementKeyDown);

This line associates the keydown event with the ImplementKeyDown function in TrainingStudioSupport.js. This allows us to go forward with the PageDown key and backwards with the PageUp key. It also shows the Comment Editor if the reviewOnvariable is true and the user presses Ctrl + Shift + M.

});

function hotspotClickHandler(e) {
	var targetId = $(this);

We use the jQuery selector, $(this), to figure out which hotspot (span) the user interacted with.

	var displayFieldId = $("#displayField");

Similarly, we make a reference to our “display field” object. We use this to set its text based on a naming scheme. When the user clicks on hotspot_1, we want to display any text in content_1.

	var hotspotName = targetId.attr("id");
	var hotspotNum = parent.getFieldNum(hotspotName);

We grab the id using the jQuery attr() function. We then find the associated number in order to work our naming scheme. hotspot_1 goes with media_1, graphic_1, and content_1and so on.

	if (lastHotspot != null) {
		lastHotspot.attr("class", "HotspotCompleted");
	}

The first time through, the lastHotspot variable will be null. After that, it will refer to the hotspot previous to this interaction. In that case, we set its class to “HotspotCompleted.” This is how we get it to turn blue or otherwise show completion.

	lastHotspot = targetId;

We set the lastHotspot variable so we’ll be able to change its class the nexttime through.

	targetId.attr("class", "HotspotCurrent");

We change the class of this hotspot to “HotspotCurrent” to denote which one we are currently looking at.

	completionArray[hotspotNum - 1] = true;

We set the associated element (subtracting 1 since the array is zero-based) of our completionArrayto true. Once all the elements are “true,” the page is completed.

	parent.showTextImageMedia(displayFieldId, hotspotName, $("#graphic_0"), $("#media_0"), true); // include media

We call the showTextImageMedia method to display the associated content, play any associated media, and display any associated graphic. Note that we pass the object references to display the content (displayFieldId), show the graphic ($(“#graphic_0”)), or play the media ($(“#media_0”)). The parameter at the end determines whether to include media.

	parent.getHotspotCompleted(completionArray, numHotspots);

We pass our completionArray and the numHotspots variable to the getHotspotCompletedmethod. This will show a “completion” image if all the interactions are completed.

}

SCORM and Closing the Browser Window: Silverlight

As discussed in the previous post on ActionScript, we needed to change our logic somewhat to support showing our Exam Engine and Training Studio content in a Tracker.Net 5 (or other LMS) frame. Since Exam Engine has both a Flash/Flex implementation and a Silverlight implementation (note: version 4 is now HTML/JavaScript), I thought I would show the Silverlight implementation in this article. The logic is the same as with the previous article. We want to initiate the SCORM messages when the user clicks the Exit button and then be sure not to resend them when the window closes. In the popup window situation, however, the SCORM messages need to be sent when the user closes the browser window without clicking the Exit button.

The Exit button code is shown below.

Private Sub ExitBtn_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    Dim examRefId As ExamEngineSettings = ExamReferenceId
    Dim exitMessage As String = examRefId.ReadExamSetting("ExitMessage", "")
    Dim okToExit As Boolean = True

    If exitMessage <> "" Then
        okToExit = Browser.HtmlPage.Window.Confirm(exitMessage)
    End If
    If okToExit = True Then
        ' Changed the following since this did not work if the content is in an iFrame.
        ' Instead, call ExitExam directly and set a variable to skip the exit on the
        ' subsequent closing of the browser window
        Dim exitSuccess As Boolean = examRefId.ExitExam()

        If exitSuccess = True Then
            examRefId.AlreadyExited = True
            Browser.HtmlPage.Window.Eval("window.close()")
        End If
    End If
End Sub

We again read our exit message and display that to the user via JavaScript. Notice how we can call the JavaScript confirm method directly from the Visual Basic code. If the user confirms exiting, we call the ExitExam method. This is where all the SCORM messages are sent. If that returns True, we set the AlreadyExited global variable to True and try to close the window.

As with the Flash/Flex implementation we registered the “onunload” browser event. In this case, we call ExitExamHandler as shown below. Notice that we check the same AlreadyExited variable before calling ExitExam. This avoids it being called twice if the user clicked the Exit button to close the window.

Private Sub Application_Startup(ByVal o As Object, ByVal e As StartupEventArgs) Handles Me.Startup
    Me.RootVisual = New Page()
    HtmlPage.Window.AttachEvent("onunload", AddressOf ExitExamHandler)
End Sub

Public Sub ExitExamHandler(ByVal sender As Object, ByVal e As EventArgs)
    Dim examRefId As ExamEngineSettings = ExamReferenceId

    If examRefId.AlreadyExited = False Then
        ExamReferenceId.ExitExam()
        examRefId.AlreadyExited = True
    End If
End Sub

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;
    }
}