Sunday, March 20, 2011

How to detect when an "object" is clicked on an HTML Canvas

The HTML Canvas allows us to render custom graphics on a web page.  The following screenshot shows a canvas with some small black rectangles drawn on its surface.  A web dialog is shown whenever the user clicks on one of the black rectangles:



The canvas element allows us to create rich, dynamic, custom drawn web displays such as this:


A common requirement when drawing custom user interfaces is likely to be that users will want to be able to interact with the display via mouseover or clicking actions.  That is not something that is provided out of the box with the canvas element. 

For example, when displaying a custom-drawn statistics chart on a canvas, the user might like to get additional information via a pop-up dialog when they place their mouse over certain parts.  This is common in rich info-graphic displays.

To include this functionality requires custom positional logic to be run to ascertain whether the location of the mouse event intersects with the location of an underlying piece of rendered drawing.  This is commonly referred to as "hit test" logic

To get started, you need to store positional information about any rendered objects that you want to be able to detect.  That is, if you draw a circle on the canvas, you will need to store the position of that circle in your code.  To demonstrate this, let's look at the code used for displaying those rectangles in the image shown above.

First, let's add a canvas to our HTML page like so:

<canvas id="board" width="150" height="150"></canvas>
Then we add the code that is responsible for displaying the rectangles and storing their positional information in memory.

function Item(left, top, width, height) {
    this.Left = left;
    this.Top = top;
    this.Width = width;
    this.Height = height;
}

var items = [];

for (var i = 0; i <= 150; i += 15) {
    var item = new Item(i, i, 5, 5);
    items.push(item);
    Draw(item);
};

function Draw(item) {
    var canvas = document.getElementById('board');
    if (canvas.getContext) {
        var ctx = canvas.getContext('2d');
        ctx.fillRect(item.Left, item.Top, item.Width, item.Height); // x, y, width, height
    }
}
Here we can see that we simply create a few Item instances, store them in an array, and then draw them as rectangles on the canvas using the fillRect method of the canvas's context object.

Next, we need to hook up an event handler to an event.  For this demo I will wire up a handler to the mouse-click event of the canvas object itself.  Here is the code for that event handler with the contained logic for performing the hit test:
$('#board').click(function (e) {
    var x = e.pageX - this.offsetLeft;
    var y = e.pageY - this.offsetTop;
    var clicked = new Item(x, y, 1, 1);
    var pos = getPositions(clicked);
    
    for (var i = 0; i < items.length; i++) {
        var pos2 = getPositions(items[i]);
        var horizontalMatch = comparePositions(pos[0], pos2[0]);
        var verticalMatch = comparePositions(pos[1], pos2[1]);
        if (horizontalMatch && verticalMatch) {
            alert("Clicked!");
        }
    }
});
You can see that, at the top of this method, we get the positional information of the click event.  Note that this information is relative to the canvas itself as we remove the canvas's offsetLeft and offsetTop from the left and top points of the event.  What this means is that, in our context, x,y will be relative to the top-left corner of the
canvas and not of the page - e.g. 0,0 would be the extreme top-left of the canvas.

Before showing the two helper functions - getPositions and comparePositions - I will explain the simple process that we go through to determine if the positions intersect.  Take a look at the two rectangles in the following diagram:


Here we can see that there is indeed an intersection point between these two rectangles.  The code that will be used to determine this can be seen below, and I grabbed this code from here.

function getPositions(item) {
    return [[item.Left, item.Left + item.Width], [item.Top, item.Top + item.Height]];
}

function comparePositions(p1, p2) {
    var x1 = p1[0] < p2[0] ? p1 : p2;
    var x2 = p1[0] < p2[0] ? p2 : p1;
    return x1[1] > x2[0] || x1[0] === x2[0] ? true : false;
}
The logic shown here is very simple and you can easily do a mental walkthrough against the diagram to see for yourself how it works.  Essentially it does this.  It first tests the left side of the leftmost rectangle and checks to see whether the right edge of that rectangle falls inside the right edge of the rightmost rectangle.  It then performs the same logic for the top and bottom sides.  Very nice and simple!

This article has shown a very simple method for doing a hit test on a custom drawn object.  In following articles, I will show how to cater for more complex scenarios where you might want the user to be able to interact with the underlying drawn object - e.g. handle a mouse-click to display a dialog that allows the user to select a new property for a certain facet of the underlying object such as the size of the object, or it's color.

Tuesday, March 8, 2011

MSTest data driven tests - Using Excel 2007 as a data source

Today I had to troubleshoot a problem that we were experiencing with data driven tests not running on the build server.

Basically, we had done a copy/paste from the web to wire-up a data source to point at a spreadsheet of data:
[DataSource(
    "System.Data.Odbc", 
    "Dsn=Excel Files;dbq=|DataDirectory|\\Data\\mydata.xlsx;defaultdir=\\Data;driverid=1046,maxbuffersize=2048;pagetimeout=5", 
    "MySheet$", 
    DataAccessMethod.Sequential)]
This is the stock standard example that you see for configuring an Excel data source around the web.  Other versions that you come across quite often, include using a Jet Driver provider or, reverting to one of the Visual Studio test tools providers (e.g. CSV or XML).

The issue that we faced was that, using the Odbc driver, tests were running fine on our developer machines (Windows 7) but were failing with the following message on our build server (Windows 2008 R2):

The unit test adapter failed to connect to the data source or to read the data. For more information on troubleshooting this error, see "Troubleshooting Data-Driven Unit Tests" (http://go.microsoft.com/fwlink/?LinkId=62412) in the MSDN Library.
Error details: ERROR [IM002] [Microsoft][ODBC Driver Manager] Data source name not found and no default driver specified
What I presume is occurring is that Windows 7 must include a whole lot of drivers that make backwards compatibility easier while Win2k8R2 only installs a minimal set.  And therefore the older Jet and OleDb drivers were not installed on the server.

The fix is to simply use the current OleDb provider for Office 2007 formatted documents.  The final solution for our data source looked like this:

[DataSource(
    "System.Data.OleDb",
    "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=mydoc.xlsx;Persist Security Info=False;Extended Properties='Excel 12.0 Xml;HDR=YES'",
    "MySheet$", 
    DataAccessMethod.Sequential)]
[DeploymentItem("\\Data\\mydoc.xlsx")]
[TestMethod]
public void MyTestMethod()
{
   ...
}

Friday, March 4, 2011

ASP.NET MVC: How to emit dynamically generated Javascript

Using ASP.NET WebForms, it is common to use the ClientScriptManager to act as a bridge between the page and the server for carrying out operations relating to Javascript.  A common example is to register an object graph as a Javascript array on the client:
var serializer = new JavaScriptSerializer();
var js = string.Format(
    "var arr = {0};",
    serializer.Serialize(foo));

var cs = Page.ClientScript;

if(!cs.IsClientScriptBlockRegistered(this.GetType(), "arr"))
{
    var sb = new StringBuilder();
    sb.Append(@"");
    cs.RegisterClientScriptBlock(this.GetType(), "arr", sb.ToString(), false);
}

In the ASP.NET MVC world, we approach this task somewhat differently.   Using the resource-based model of MVC, we can get a cleaner implementation without having to use the ScriptManager to act as a middle-man.

What we want to end up with is to have our Javascript be requested just as any other static resource in our page would be.  So we can start by modeling that approach with a script tag pointing to a url:
<script src="@Url.Content("~/app/scripts/usertags")" type="text/javascript"></script>
Next, we must create a controller and an action that will serve up the dynamic Javascript content.
public class ScriptsController : Controller
{
    public JavaScriptResult UserTags()
    {
        var accountId = ((AppIdentity)this.User.Identity).Id;
        var s = "alert(\"Account: " + accountId + "\");";
        return JavaScript(s);
    }
}
Using this approach we can group all of our dynamically generated scripts together and therefore manage and test them accordingly without having the whole page framework getting in the way.  In the above example you can see that I read a property off of my custom application identity and return it as some Javascript.

If you compare what's happening between the MVC approach and the previous approach, you can see that the code very similar.

The final thing to do would be to map a route so that the request gets mapped to the correct controller action.