Playing a Video in the HTML 5 Era

I was setting up a Tracker.Net demonstration today and the customer sent me a .wmv file to be used as one of the lessons. Although a video like this would normally be added to a lesson created by an authoring tool, Tracker.Net also allows any file or HTML link to be an asset, meaning that the user can launch it. It is then automatically marked as complete since there is no SCORM communication.

While it is possible to launch a .wmv file directly, I prefer not to do that since then you are relying on the user’s association with that extension. The result can be Windows Media Player or any of a myriad of players. These can take quite a while to spin up and can make your training seem unresponsive. Instead, I like to embed the video in a simple HTML page. In the past, I would have just pasted in the tags for a Windows Media Player, but with the advent of HTML 5, it is better to play the video natively in the browser. This is faster and a better cross-platform approach, particularly with mobile devices. And older browsers will default to the Windows Media Player anyway. Here’s how to do.

  1. Convert the .wmv files to the other video formats: mp4 (Internet Explorer and Safari), ogg/ogv (Firefox and Opera), webm (Firefox, Chrome, and Opera). You can see a list of what browsers support what formats at http://en.wikipedia.org/wiki/HTML5_video. There are numerous programs and sites for doing the conversion. I’ve found the Freemake Video Converter and Freemake Audio Converter (to do the same thing with audio files) work well.
  2. Create your HTML page with the HTML5 video tags first but with the Windows Media Player ActiveX control tags (Internet Explorer) and embed tags (Firefox and other browsers) as shown.
<!doctype html> 
     <html>  
          <head>   
               <title>Measuring Voltage</title>  
          </head>  
          <body>   
               <form>    
                    <video controls="controls" autoplay="autoplay">     
                         <source src="Measuring_Voltage.webm" type='video/webm; codecs="vp8.0, vorbis"'/>     
                         <source src="Measuring_Voltage.ogv" type='video/ogg; codecs="theora, vorbis"'/>     
                         <source src="Measuring_Voltage.mp4" type='video/mp4; codecs="avc1.4D401E, mp4a.40.2"'/>     
                         WordPress won't display the windows media tags correctly. Click here to see a graphic of the tags.
                    </video>   
               </form>  
          </body> 
</html>

Newer browsers will play the video format that they can support. Older browsers will revert to the Windows Media Player.

I hope this is helpful.

Advertisements

Using jQuery UI Along with ASP.NET Web Forms

I like the jQuery UI objects, particularly its Button and Datepicker widgets. But what if you want to use them on a normal ASP.NET web form? Back in the early 2000’s, I wrote the VBTrain Graphical Button control to generate all the JavaScript needed to swap out graphics for the up, down, over, and disabled states. But now jQueryUI does all of that for us. Making the two technologies work together is pretty simple. Here is what the input control looks like:

<input id="SubmitBtn" runat="server" value="Submit" type="submit" />

The only change we made was the runat=”server” part. This is what allows us to recognize it in server-side code.

The next step is to add jQuery UI. We do this with the appropriate .js and css files loaded in the head of the file. We are using the Start theme, which is why the reference is “css/start/.”

<link href="css/start/jquery-ui-1.8.17.custom.css" rel="stylesheet" type="text/css" />
<script src="scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
<script src="scripts/jquery-ui-1.8.17.custom.min.js" type="text/javascript"></script>
<script type="text/javascript">
    $(function () {
        $("input:submit").button();
    });
</script>

You might recognize the “load” function from the previous article. Once the page is fully loaded, it executes. We then use a new type of selector: “input:submit”. This means that we find all input controls that have a type of submit. We then call the button() method on each one. This is what loads all the appropriate graphics and associated scripts. If there had been 10 input buttons (of type submit) on the page, that one line would have configured all of them.

The last step is handling the click in our “code behind” file. That handler is shown below.

Private Sub SubmitBtn_ServerClick(sender As Object, e As System.EventArgs) Handles SubmitBtn.ServerClick
    ' take action here
End Sub

Those of you who are used to normal ASP.NET buttons will see that the event is a bit different: ServerClick rather than the normal Click. But the functionality is exactly the same.

JavaScript and jQuery in Exam Engine 4

The new version of Exam Engine is completely HTML and JavaScript. The navigation buttons and other common elements are in the main page (index.htm) as is an iFrame that contains each of the templates. One of these templates is login.htm, which is used if the developer has specified to use a login page in the absence of an LMS. Let’s look at that page to get an idea of the design and how the jQuery library comes in handy.
Here is the HTML of the login.htm page.

<body>
    <span id="loginLabel"></span>
	<img id="loginImageBtn" src="" alt="" />
	<input id="loginBtn" type="button" />
	<input id="studentName" type="text" />
	<input id="studentPassword" type="password" />
	<span id="statusBlock"></span>
</body>

The position, font, and other formatting are implemented with a style sheet. But we need to add the functionality with JavaScript. Here is the “load page” script:

var usePassword;

$(function () {
	var loginText = parent.ReadExamSetting("LoginPageMessage", "Enter your name:");
	var loginBtn;
	var studentNameId = $("#studentName");
	var passwordId = $("#studentPassword");

	usePassword = parent.ReadExamSettingBoolean("LoginPageUsePassword", false);

	$("#loginLabel").html(loginText);

	if (parent.UsejQueryUiButtons == true) {
		loginBtn = $("#loginBtn");
		$("#loginImageBtn").hide(); // needed for Firefox
	}
	else {
		loginBtn = $("#loginImageBtn");
		$("#loginBtn").hide(); // needed for Firefox
	}

	parent.ConfigureButton(loginBtn, "Login", LoginBtn_Click);
	parent.SetBtnStatus(loginBtn, "Login", true, true, true); // visible, enabled, in template

	if (usePassword == true) {
		passwordId.show();
		passwordId.keydown(studentName_KeyDown);
	}
	else {
		passwordId.hide();
		studentNameId.keydown(studentName_KeyDown);
	}

	// Hide Countdown Timer
	parent.CountdownTimerRef.hide();
	parent.TransitionControlRef.show();
});

We declare usePassword outside the function block so that we can use its value when the user actually clicks the Login button. The $(function() { syntax is part of jQuery and means that the page if fully loaded and all the objects are in place. We read our loginText variable with the ReadExamSetting function. The first parameter is the name of the setting and the second is a default value. This setting is originally created in the Exam Engine Configuration Editor and is stored in an XML file that is part of the exam. parent refers to the main page and its ExamEngineSettings.js file. The studentNameId and passwordId variables are jQuery objects. The “#studentName” is a selector that identifies an object with an id of “studentName.” This is the input control in the HTML above.

Next, we read the usePassword value from the exam as well. We then set the text of our loginLabel span control with this syntax: $(“#loginLabel”).html(loginText);. The first part creates the jQuery object using the selector as we saw above. We then call its html method and set its value to the loginText value that we read above.

Our next set of logic is to use either jQuery UI buttons or normal graphic buttons. We set the loginBtn variable to the appropriate object (input for jQueryUI or img for graphical) and hide the other object. At that point, we call two common functions, ConfigureButton and SetBtnStatus. These associate the proper “click” handler, set the states of the buttons, and load graphics if needed.

We then use our usePassword variable to either show or hide the password input control. Based on whether it is showing, we either add a KeyDown handler to it or the login input. This is so the user can press the Enter key rather than having to click or tab to the Login button. Notice the handy jQuery syntax to handle the keydown event with the studentName_KeyDown function: passwordId.keydown(studentName_KeyDown);.

We end by hiding the “countdown” timer and showing the iFrame itself (TransitionControlRef). The main point is that using jQuery simplifies getting our hands on object references and gives us a standard set of properties and methods.

Enabling or Disabling Voice Recordings

How would you write a tool like the “Voice Recording Toggle” that is available via LiveXtensions in ToolBook? The first task is to figure out how the enabling happens. If you check the “Enable voice recording playback for this page” box on the Voice Recordings tab of the Properties for Page sheet and then check the User Properties of the page using the Property Browser, you’ll see that it adds a property called TBK_VoiceRecordings. It is true if recordings are enabled and false otherwise. That leads to an implementation from the Command Window like the code below. Notice how we check whether the property of the page is NULL. That keeps us from setting the property where there is no voice recording in the first place.

isEnabled = TRUE
step num from 1 to pageCount of this book
	pageId = page num
	if TBK_VoiceRecording of pageId <> NULL
		TBK_VoiceRecording of pageId = isEnabled
	end if
end step

.NET Structures

When I am programming, I often into situations where I need to pass around related data of different types. In some languages, the best you can do is create some sort of array and then stuff the data into elements of the array and hopefully be able to convert the data in and other of strings. But languages like Visual Basic.Net have a more powerful option, a Structure. Like a full-fledged Class, a structure has its own properties and can even have its own methods.

For a custom project I was working on, I needed to read a bunch of related files and keep track of them until I wrote their data to the database. At that time, I needed to move the “successful” files to a “Success” directory, the problem files to an “Error” directory, and those that needed to be skipped to a “Skip” directory. To do this efficiently, I needed to keep track of a reference to the original file and the path to move it to. For that, I used the structure below:

    Friend Structure MoveFileStructure
        Friend FileInfoId As FileInfo
        Friend TargetPath As String
    End Structure

The structure has two properties. The FileInfoId is of type FileInfo and is a reference to the file itself. The TargetPath is the complete path to where I want to move the file later. I then carry that data around the program until I needed to work with the files. For example, the method below actually moved the files to their new location.

    Friend Sub MoveFiles(ByRef moveFileList As List(Of MoveFileStructure))
        Dim newFilePath As String
        Dim structureId As MoveFileStructure

        For Each structureId In moveFileList
            newFilePath = structureId.TargetPath
            If File.Exists(newFilePath) Then
                File.Delete(newFilePath)
            End If
            Try
                structureId.FileInfoId.MoveTo(newFilePath)
            Catch ex As Exception
                ' don't do anything
            End Try
        Next
        moveFileList.Clear()
    End Sub

We pass in a Generic List (another great concept in .NET) of MoveFileStructure objects. So each of these are instances of the structure, which means they have FileInfoId and TargetPath properties. We then loop through the list. We read the TargetPath and delete any existing file with that path. We then use the MoveTo method of our FileInfo object to move the file to the new location. We put the code into a Try-Catch block just in case the file got listed twice and has already been moved. Finally, we clear our list since the files have now been moved.

Introduction to jQuery

I have been doing quite a bit of HTML and JavaScript and am very impressed with the jQuery library. It is free and is being adopted widely. I like it because 1) it keeps you from having to worry about cross-browser differences, 2) it makes coding in JavaScript more like using Visual Basic or OpenScript. Here is a short example to get you started. The HTML below has two buttons and four images. We want to show all the images when you click the showBtn and hide them when you click the hideBtn.

<body>
    <input id="showBtn" type="button" value="Show Images" />
    <input id="hideBtn" type="button" value="Hide Images" />
    <img id="Img1" src="media/help.png" />
    <img id="Img2" src="media/coda.png" />
    <img id="Img3" src="media/Desperado.png" />
    <img id="Img4" src="media/5150.png" />
</body>

Let’s write the logic with jQuery. We first add a reference to its .js file:

<script src="jquery/jquery-1.5.1.js" type="text/javascript"></script>

Next, we add the code below.

    <script type="text/javascript">
        $(document).ready(function () {
            $("#showBtn").click(function (e) {
                $(".ImageClass").show();
            });
            $("#hideBtn").click(function (e) {
                $(".ImageClass").hide();
            });
        });
    </script>

The $ symbol is the shortcut for making a jQuery call. The parameter is either an object like document or a selector like we will see below. So $(document) is a jQuery object that references the HTML document. We then define what to do when it is ready. This means that the page is fully loaded. This equivalent to Load in Visual Basic or enterPage in ToolBook OpenScript.

When ready, we configure what our two buttons do. This starts to show the real power of jQuery. The $(“#showBtn”) code gets the jQuery object reference to an HTML object with an id of “showBtn.” This is our button. We then define what to do in response to its click event. In that case, we use another jQuery selector, $(“.ImageClass”). The “.” means that we are looking for all objects with a class of “ImageClass.” We then call its show method. Notice that even though there are four of the objects (our images), jQuery takes care of calling the show method for each one. jQuery also takes care of how to show the object based on its type and the browser. This typically means changing the object’s display style.
Similarly, we find the hideBtn and defined to hide all the image objects in response to its click event.

This just scratches the surface of what jQuery can do. But hopefully, it gives you an incentive to learn more.

International Date Formats

Question: I need to get out of my provincial ways and format dates appropriate to the local preference (i.e., US, European, etc). Are there any functions I could take advantage of in ToolBook to make my path easier for localization of date formats?

The function below from our TBK Tracker product should be helpful. In early versions, we ran into problems with dates stored in an Access database on the user’s machine being in a different format than what we expected in ToolBook. This was particularly true in regions that put the day first (25/2/2014 as opposed to 2/25/2014).

to handle tbk_setSysDateFormat
	-- if not ASP mode, converts registry date 
	-- format to ToolBook equivalent
	-- else gets ToolBook date format from ASP 
	-- page

	system string s_originalSysDateFormat, 
		s_fileDir, s_tbk_iniFile, s_tbk_dbType

	local string newFormat, blkSus, errorString, aspPage
	local word offsetNum

	if s_originalSysDateFormat <> NULL 
		-- already set
		break
	end if

	if s_tbk_dbType = "ASP" -- get date format 
		-- from server-side ASP page
		aspPage = tbk_getASPPage("formatData")
		get platte_SetHTTPPostParameter("formatType", "date")
		s_originalSysDateFormat = platte_HTTPPost(aspPage)
		if s_originalSysDateFormat = null
			s_originalSysDateFormat = "mm/dd/y"
		end if
	else -- use date format of client machine
		blkSus = ASYM_blockSuspend()
		newFormat = RegistryGetKey("HKEY_CURRENT_USER\ControlPanel\International", \
		    "sShortDate")
		errorString = ASYM_restoreSuspend(blkSus)
		if (errorString <> null) OR (newFormat = null) 
		    -- unable to read date format 
			-- from registry; try win.ini
			newFormat = getWinIniVar("intl", "sShortDate")
			if (newFormat = null) OR (ASYM_isNumber(newFormat) = TRUE) 
				-- unable to read from ini file
				s_originalSysDateFormat = sysDateFormat -- default
				break
			end if
		end if

		-- year
		offsetNum = offset("yyyy", newFormat)
		if offsetNum <> 0
			chars offsetNum to (offsetNum + 3) of newFormat = "y"
		end if

		-- month
		if offset("MMM", newFormat) = 0 -- leave 
			-- alone if contains MMM; else make 
			-- lower case
			newFormat = lowerCase(newFormat)
		end if

		-- day
		-- no conversion req'd

		s_originalSysDateFormat = newFormat
	end if
end tbk_setSysDateFormat

You can skip the “ASP” part. The rest reads the registry and uses that to set the s_originalSysDateFormat system variable. When it comes time to display a date, we temporarily set sysDateFormat to this variable. The rest of the time, we set sysDateFormat to “seconds” since we use the sysDate for time tracking. You likely could just set sysDateFormat once.