Using JavaScript and jQuery to Configure an Anchor Tag

We recently helped one of our Exam Engine customers with a custom question template. In this template, they wanted to have a smaller version of an image. The user could then click on the smaller image to see a larger version in a popup window. The first task was to add the anchor tag to the HTML as shown below. The new lines are shown in bold.

<a id=”questionGraphicLink” target=”questionGraphicWindow”>
    <img id=”questionGraphic” src=”” alt=”” />
</a>

Next, we used the fact that the larger image would match the name of the smaller image except for a _lg at the end of the name. So if the question image were “xyz.png”, then the large image would be “xyz_lg.png.” We thus added the code below to the function that is called when the page is fully loaded.

// make hyperlink out of questionGraphic
            if (questionDictionaryId["QuestionGraphic"] != null) {
                var lgGraphic = questionDictionaryId["QuestionGraphic"].replace(".png", "_lg.png");

                $("#questionGraphicLink").attr("href", ("../media/" + lgGraphic));
            }

The questionDictionaryId JavaScript objects holds all the items for the question. If it has a “QuestionGraphic” item, we put a reference to the large graphic in the lgGraphic local variable. We then use jQuery to find a reference to the questionGraphicLink anchor object. We then set its href attribute to a relative path to that graphic. If the question does not have a graphic, we skip this step and thus avoid any kind of broken links.

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.

Responsive Web Design Book

I’m getting ready to work on a major design of plattecanyon.com and our other web sites. I like the concept called “responsive web design” where the site adjusts automatically to different browser capabilities and device sizes. I’m finding this book quite helpful: Responsive Web Design with HTML5 and CSS3.

Even better, I am reading it on my Kindle and can click on the sample links and check them out in real-time. When I open the book in my Kindle for Windows 8 application, I can view it on one monitor and have Visual Studio 2012 on the other monitor. Nice!

Hit By SQL Injection Attack

I hate to admit it, but we were hit by this SQL Injection Attack last week. The gist of a SQL Injection Attack is that the attacker hijacks your database query and inserts his own query that displays your data on the screen, wipes out your database, or other action. This particular attack wipes out every row and column in EVERY table in your database and replaces its content with a URL and script tags in the hope that your site will then be filled with links to the rogue web site. The best defense against this type of attack is to use parameterized queries/stored procedures rather than a “dynamic” SQL statement. This keeps the “bad” query from actually executing. Instead, the bad info is passed in as a parameter and the procedure then just fails or returns no data.

If I know how to prevent an attack, how did this attack work on our site? The short answer is that I was dumb. The longer answer is that most explanations of this type of attack demonstrate with a username and password-type page. You enter your username and password on a page. The insecure way to code this is to dynamically have code like this:

Dim sqlStatement As String = String.Format("Select * From Users Where username = '{0}' AND password = '{1}'", usernameField.Text, passwordField.Text)

The attacker then puts in a big set of SQL code in the username field that comments out your own statement and does what he wants with the statement. I went through every page and application on the site that got hit and couldn’t find anywhere where we were building dynamic SQL like this. But then a great support member from our hosting company, DiscountASP.NET, looked through the logs and found the problem. It was with a particular design that we originally used with our VBTrain.NET controls. It passed the productId in as part of the query string. For example:  http://www.vbtrain.net/productDisplay.aspx?id=9. There are no active links to this page, but they were still on the site and working. The attack came when the rogue site sent the bogus SQL Statement (encoded) in place of the 9 above. So the URL looked like this:

productDisplay.aspx id=6%29+declare+%40s+varchar%284000%29+set+%40s%3Dcast%280x73657420616e73695f7761726e696e6773206f6666204445434c41524… rest removed for security reasons.

If I had used stored procedures for the query to get the data, we still would not have been vulnerable. Unfortunately, when I first set these sites up, I was reading data from various tables and decided to have a common method where I passed in the table, column, default value, etc. Here was the original method:

Protected Function RetrieveDataReader(ByVal tableName As String, ByVal columnName As String, ByVal columnValue As String, Optional ByVal sortColumn As String = "notAColumn") As SqlDataReader
Dim conId As SqlConnection = Me.VBTrainConnectionId
Dim dbString As String = String.Format("Select * from {0} where ({1} = {2})", tableName, columnName, columnValue)
If sortColumn <> "notAColumn" Then
dbString &= String.Concat(" order by ", sortColumn)
End If
Dim dbCommand As New SqlCommand(dbString, conId)
Dim dbReader As SqlDataReader = dbCommand.ExecuteReader(CommandBehavior.CloseConnection)
Return dbReader
End Function

We needed to use dynamic SQL in this case since it is hard if not impossible to write a stored procedure where the name of the table and the columns vary. We then called the function like this:

Dim dbReader As SqlDataReader
dbReader = Me.RetrieveDataReader("productInfo", "productID", idVal)

idVal was what we grabbed off the query string (everything after the ?). Those of you who have made it this far can probably see how this was a huge security hole. The other pages using this function had hard-coded values, which were safe. But passing in idVal straight from the URL allowed the hacker to blow away our database. So what to do?

  1. The best thing would be to change the design so that it calls a stored procedure instead. We will do this on the next redesign.
  2. For now, we can eliminate the vulnerability by checking the parameter before passing to the RetrieveDataReader function. We now make sure the parameter is a number AND that it is a very short length before allowing it through. Otherwise, we use a known value such as the 9 above.

Hopefully someone will read this and use it to protect your sites better than we did. I created the pages in question back in 2002. So another lesson is to go back and look at your old sites and applications and check them for security vulnerabilities.

On the positive side, we have the master copies of the databases in question saved locally and were able to recover quickly by using SQL Data Compare from Red Gate Software to update the database that had gotten hacked.