ASP.NET MVC: Unauthenticated User Always Redirected to ~/Account/LogOn Despite Custom Sign In Url


By default new ASP.NET MVC projects redirect any unauthenticated users who request resources that require authentication to the /Account/LogOn action. The log on page is defined in the web.config authentication section.

<authentication mode="Forms">
  <forms loginUrl="~/Account/LogOn" timeout="2880" />
</authentication>

It is my preference for users to “Sign in” instead of “Log on” so I changed the web.config to the following and changed the action method name from LogOn to SignIn.

<authentication mode="Forms">
  <forms loginUrl="~/Account/SignIn" timeout="2880" />
</authentication>

After the change I tried accessing a page that required the user to be authorized with and unauthorized user and to my surprise, the site redirected me to /Account/LogOn and I got a 404 error saying the the resource could not be found. It turns out this is a known issue with the ASP.NET MVC when the WebMatrix.Data.dll and WebMatrix.DataWeb.dll are added to the deployable assemblies collection. This issue can be fixed by adding the following key to the appSettings section of the web.config.

<appSettings>
  ...
  <add key="loginUrl" value="~/Account/SignIn" />
</appSettings>

I presume this will be fixed in the next release of ASP.NET MVC as a bug has been logged on Microsoft Connect here.

Unit Testing: Verify a Method Call Ignoring the Parameters


I use Moq extensively when unit testing my applications. One of the nice features of Moq (and any other Mocking framework) is that you can verify which methods were called on a given interface during a test. By default, if you have a method on an interface that accepts parameters and you want to verify that it is called, you have to specify the exact parameters that should have been used in the method call for it to pass the test. Example:

public interface ICompanyService
{
    void Add(Company company);
}

[Test]
public void SignUp_Action_Calls_CompanyService_Add_Method()
{          
    // Arrange       
    var companyService = new Mock<ICompanyService>();     
    AccountController controller = new AccountController(companyService.Object);

    // Act
    var model = GetSignUpViewModel();
    var result = controller.SignUp(model);
    
    Company company = new Company();
    company.Name = model.Name;
    company.Address = model.Address;
    company.PhoneNumber = model.PhoneNumber;

    // Assert    
    companyService.Verify(x => x.Add(company));
}

If you need to test the actual data be passed to the Add method you can do this but if you just want to test whether or not the method was called regardless of the parameters supplied to the method, you can using Moq. To do this, just use It.IsAny method supplied by the Moq framework to indicate that you just want to see if the method was called with any type of Company object.

[Test]
public void SignUp_Action_Calls_CompanyService_Add_Method()
{          
    ...

    // Assert    
    companyService.Verify(x => x.Add(It.IsAny<Company>()));
}

Mocking frameworks are your friends and save hours of time if you know how to use them correctly.

Dependency Injection: Ninject – Registering Two Services to the Same Instance of an Object


In on of my recent ASP.NET MVC projects I was implementing the UnitOfWork pattern while utilizing the Entity Framework Code First approach for data access. I have seen many examples of the UnitOfWork pattern where the repositories are actually contained within the UnitOfWork class but since the Entity Framework already supports the UnitOfWork pattern out of the box, I decided to go with a different approach.

To jump to the solution to the problem, click here.

Using the Entity Framework Code First approach, a class inheriting from the DbContext class is created and is used to handle all the database interaction. Whenever you perform any work using the DbContext, the changes are not actually persisted to the database until you call the SaveChanges method. As I said, the UnitOfWork pattern already implemented. To enforce the UnitOfWork pattern, I created an IUnitOfWork interface and ensured that my DbContext class implemented that interface.

public interface IUnitOfWork
{
    void Commit();
}

public class DatabaseContext : DbContext, IUnitOfWork
{
    public DbSet<Company> Companies { get; set; }
    public DbSet<Profile> Profiles { get; set; }
    ...
 
    public DatabaseContext ()
    {
        
    }

    public DatabaseContext (string connectionString)
    {
        this.Database.Connection.ConnectionString = connectionString;
    }

    public void Commit()
    {
        base.SaveChanges();
    }
}

Each of the repositories in the application accepts a class that inherits from DbContext and uses that for all database interaction.

public class EntityFrameworkRepository<T> : IRepository<T> where T : class, IEntity
{
    private DbContext context;
    private DbSet<T> dbSet;

    public EntityFrameworkRepository(DbContext context)
    {
        if (context == null) { throw new ArgumentNullException("context"); }
            
        this.context = context;
        this.dbSet = this.context.Set<T>();
    }
}

I then have a service layer that accepts any number of repositories and lastly, each Controller accepts any number of services and a IUnitOfWork implementation.

public class AccountController : BaseController
{
    private IUnitOfWork unitOfWork;
    private IAccountService accountService;
    private IProfileService profileService;

    public AccountController(IUnitOfWork unitOfWork, IAccountService accountService, IProfileService profileService)
    {
        this.unitOfWork = unitOfWork;
        this.accountService = accountService;
        this.profileService = profileService;
    }
}

This allows the Controller to interact with any number of repositories and then after all operations are complete, to call the IUnitOfWork.Commit() method. Here is the issue. With this implementation, the IUnitOfWork instance supplied to the AccountController and the DbContext provided to the repositories all need to be the same instance of the DatabaseContext class for the UnitOfWork pattern to work. Here we my first attempt at doing this.

private static void RegisterServices(IKernel kernel)
{           
    kernel.Bind<IUnitOfWork>().To<DatabaseContext>();
    kernel.Bind<DbContext>().To<DatabaseContext>();
}   

This definitely didn’t work as it created new instances of the DatabaseContext class for each repository and each IUnitOfWork. Using Ninject, to ensure that the same DbContext instance is used for all dependencies in a single http request, you simply need to add InRequestScope() to the end of the registration. So I tried the following.

private static void RegisterServices(IKernel kernel)
{           
    kernel.Bind<IUnitOfWork>().To<DatabaseContext>().InRequestScope();
    kernel.Bind<DbContext>().To<DatabaseContext>().InRequestScope();
}   

This was closer as all the repositories were using the same DbContext but when the Controller requested an IUnitOfWork, Ninject created a new instance of the DatabaseContext class and thus the IUnitOfWork pattern did not work. I needed to somehow ensure that the same instance of the DatabaseContext class was used for all DbContext and IUnitOfWork requests. After some research, I figured out that the following code will resolve the problem.

private static void RegisterServices(IKernel kernel)
{       
    // This gives us the ability to perform multiple operations
    // on multiple repositories in a single transaction.
    kernel.Bind<DatabaseContext>().ToSelf().InRequestScope();
    kernel.Bind<IUnitOfWork>().ToMethod(ctx => ctx.Kernel.Get<DatabaseContext>());
    kernel.Bind<DbContext>().ToMethod(ctx => ctx.Kernel.Get<DatabaseContext>());
}   

In the code above, we are first binding the DatabaseContext class to itself indicating that only one instance should be created per http request. This ensures that whenever a DatabaseContext instance is required, the same one is used. Then, to bind that same DatabaseContext instance to both IUnitOfWork and DbContext request, we need to bind those requests to a method which requests a DatabaseContext instance from the Ninject kernel. And that’s it!

ASP.NET MVC: UserProfile Using the SqlTableProfileProvider


Many have discussed the woes of working with the default ASP.NET SqlProfileProvider in a web application as the profile information is pretty much impossible to query. Gratefully another profile provider was developed, the SqlTableProfileProvider, that stores user profile information in a table format with one column per profile property. The code for that can be found here. But alas, there is still one more issue when using the SqlTableProfileProvider when developing an ASP.NET MVC application. Since it is a web application project and not a web site project, you lose the convenience of the strongly typed Profile object when coding. Again, another work around has been created that solves this problem here. Unfortunately the previously cited example assumes you are using the default SqlProfileProvider and not the SqlTableProfileProvider. I wanted to just name a few changes that need to be made when using the cited Profile Provider in an ASP.NET MVC application with the SqlTableProfileProvider.

First, since you are specifying the profile properties in the UserProfile class, you need to remove the profile property definitions from the web.config profile section. Instead of your profile section looking like this:

<profile enabled="true" defaultProvider="TableProfileProvider" inherits="App.Domain.Models.Account.UserProfile">
  <providers>
    <clear />
    <add name="TableProfileProvider" type="Microsoft.Samples.SqlTableProfileProvider" connectionStringName="Database" table="Profiles" applicationName="/" />       
  </providers>
  <properties>
    <add name="FirstName" type="string" defaultValue="" customProviderData="FirstName;nvarchar" />
    <add name="LastName" type="string" defaultValue="" customProviderData="LastName;nvarchar" />
  </properties>
</profile>

Your profile section will not have the properties section and will look like this:

<profile enabled="true" defaultProvider="TableProfileProvider" inherits="App.Domain.Models.Account.UserProfile">
  <providers>
    <clear />
    <add name="TableProfileProvider" type="Microsoft.Samples.SqlTableProfileProvider" connectionStringName="Database" table="Profiles" applicationName="\" />       
  </providers>
</profile>

If you don’t remove the properties section, you will get a ConfigurationErrorsException at run-time saying “This profile property has already been defined.”

Second, while the UserProfile class will define which properties are available in the profile, you still need to specify the CustomProviderData information that gives the provider the column name and type for each property. To do this, just add the CustomProviderData attribute to each property in the UserProfile class.

public class UserProfile : ProfileBase
{
    [SettingsAllowAnonymous(false), CustomProviderData("FirstName;nvarchar")]
    public string FirstName
    {
        get { return base["FirstName"] as string; }
        set { base["FirstName"] = value; }
    }

    [SettingsAllowAnonymous(false), CustomProviderData("LastName;nvarchar")]
    public string LastName
    {
        get { return base["LastName"] as string; }
        set { base["LastName"] = value; }
    }
}

With those two changes you should be up and running with a strongly typed Profile object using the SqlTableProfileProvider.

ASP.NET MVC: Ensure Session Variable is Always Populated


In a recent project, I needed to ensure that a session variable was populated for every View. Using ASP.NET WebForms one could perform a check for the session variable in the Page_Load event of a master page but this obviously doesn’t work for the MVC Framework as there is no such page life cycle. Despite the lack of a “page life cycle” like WebForms, there is still a request life cycle in which a number of events are fired before and after the controller action.

To solve our problem we need to do two things:

  1. Create a base controller from which all other controllers will be derived
  2. Override the OnActionExecuting event in the base controller

In the following code I have done both the named items above. I have created a class named BaseController that extends the Controller class and have overridden the OnActionExecuting event which will fire each time an action method is requested. Within this event, you simply check the session variable you need populated and if it does not have a value, populate it.

public class BaseController : Controller
{
    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);

        // If the current user is authenticated, check to make sure the user's
        // profile has been loaded into session
        if (User.Identity.IsAuthenticated && HttpContext.Session["UserProfile"] == null)
        {
            Session["UserProfile"] = UserProfile.GetUserProfile(User.Identity.Name, true);
        }
    }
}

Now when generating our Controllers for the application, instead of extending the Controller class, just extend the BaseController class we created above.

public class HomeController: BaseController
{
    public ActionResult Index()
    {
        // Since we are extending from our custom BaseController class
        // the OnActionExecuting event will be fired and the session variable
        // will be populated
        return View();
    }
}

You can also simply override the OnActionExecuting event for a single Controller if the logic doesn’t need to span multiple Controllers but be care not to duplicate code if doing this.

Windows Gadget: WordPress Statistics v1.0.4


It appears that WordPress has recently changed the way in which they deliver blog statistics through their API. This change caused the WordPress Statistics Windows Gadget that I created to break. I have submitted an update to the Windows Live Gallery and it is currently pending approval. From my experience it usually takes about one or two days for it to be approved. Please click here to check the gallery over the next few days to get an updated version of the gadget.

HTML5: Drag and Drop Multiple Objects on the Canvas Element


For those of you just tuning it, I have been working on a project to implement a tile based gamed called Bananagrams using HTML5, specifically using the canvas element and JavaScript. In my previous two posts (here and here) we discussed how to drag and drop a square tile on the canvas, how to make the tiles line up automatically so the user doesn’t have to use pinpoint accuracy, and how to make sure tiles don’t get stacked on top of one another. We have come pretty far in our development of the UI but if you try playing the game, you will find it is a bit tedious to have to drag and drop every single tile into place one at a time. In this post, we want to give the user the ability to select multiple tiles and drag and drop them all at once, as shown below.

Drawing a Dynamic Rectangle

Selecting multiple tiles can be done in a few different ways but the most intuitive way to me is to let the user draw a rectangle around the tiles they want to drag. We will assume that if the user doesn’t click directly on a tile, they are attempting to draw a rectangle and if they click on a tile, they are attempting to drag that tile or group of tiles to another location. Using this methodology we will need to add some more logic to our mouseDown, mouseMove, mouseUp, and draw functions. Lets deal with the mouseDown function first.

mouseDown Function
If we look at our current mouseDown function, the first part of the code below, as defined in part 2 we see that it loops through the tiles currently on the canvas and determines whether or not the user has clicked on one of them. We see on line 28 below that if we detect that a tile was clicked, we return and exit the function. On the other hand, if no tiles were selected, we exit the loop and continue on in the function. If we get to line 33 in the code below we know that the user has not clicked on a tile and we can assume they want to draw a rectangle.

function mouseDown(e) {
	// Get the current mouse coordinates
	getMouse(e);

	// Indicate that the user is not dragging any tiles
	isDragging = false;

	// Check to see if the user as clicked a tile
	for (var i = 0; i < tilesInPlay.length; i++) {
		var tile = tilesInPlay[i];

		// Calculate the left, right, top and bottom
		// bounds of the current tile
		var left = tile.x;
		var right = tile.x + TILE_TOTAL_WIDTH;
		var top = tile.y;
		var bottom = tile.y + TILE_TOTAL_HEIGHT;

		// Determine if the tile was clicked
		if (mouseX >= left && mouseX <= right && mouseY >= top && mouseY <= bottom) {
			// Indicate that the current tile is selected
			tilesInPlay[i].selected = true;
			isDragging = true;

			// Wire up the onmousemove event to handle the dragging
			document.onmousemove = mouseMove;
			needsRedraw();
			return;
		}
	}

	// No tiles were clicked, make sure all tiles are not selected
	clearSelectedTiles();
}

The first thing that we need to do is setup some global variables to help us when drawing the rectangle

var isSelecting = false;            // Indicates whether or not the user is drawing a selection rectangle
var selectionStartX;                // Stores the x coordinate of where the user started drawing the selection rectangle
var selectionStartY;                // Stores the y coordinate of where the user started drawing the selection rectangle
var selectionEndX;                  // Stores the x coordinate of the end of the current selection rectangle
var selectionEndY;                  // Stores the y coordinate of the end of the current selection rectangle

The first is a boolean variable that will let us know in the draw function whether or not the user is drawing a selection rectangle. The next four variables will store the starting and ending coordinates of the user drawn rectangle. Now lets update our mouseDown event to use the above variables.

function mouseDown(e) {
	// Code to determine if a tile has been clicked as defined in part 2
	...

	// ***** START NEW CODE ***** 
	// Indicate that the user is drawing a selection rectangle and
	// update the selection rectangle start and edit coordinates
	isSelecting = true;	
	selectionStartX = mouseX;
	selectionStartY = mouseY;
	selectionEndX = mouseX;
	selectionEndY = mouseY;
	
	// Wire up the onmousemove event so we can dynamically draw the rectangle
	document.onmousemove = mouseMove;	           
	needsRedraw();
	// ***** END NEW CODE *****
}

Add the above code to the end of the mouseDown event. If we get to this point, as mentioned before, the user didn’t click a tile and we are going to indicate that the user is drawing a selection rectangle and set the starting and ending coordinates of the rectangle to the current mouse coordinates.

mouseMove Function
In the mouseMove function we now need to update the ending coordinates of our selection rectangle if one is being created.

function mouseMove(e) {
	// Code to update the position of the selected tiles as defined in part two here
	...
	
	// Update the end coordinates of the selection rectangle
	if (isSelecting) {
		getMouse(e);

		selectionEndX = mouseX;
		selectionEndY = mouseY;	           
	
		needsRedraw();
	}
}

Add the above code to the end of the mouseMove event after the code we already defined to update the coordinates of the selected tiles.

draw Function
In the draw function, we now not only need to draw the tiles on the canvas but as well as a rectangle when the user is trying to select multiple tiles. Add the code on lines 20 – 23 to the draw function as shown below. I have included the entire draw function to see where to place the code.


// Draw the various objects on the canvas
function draw() {
	// Only draw the canvas if it is not valid
	if (redrawCanvas) {
		clear(ctx);

		// draw the unselected tiles first so they appear under the selected tiles
		for (var i = 0; i < tilesInPlay.length; i++) {
			if (!tilesInPlay[i].selected)
				drawTile(ctx, tilesInPlay[i]);
		}

		// now draw the selected tiles so they appear on top of the unselected tiles
		for (var i = 0; i < tilesInPlay.length; i++) {
			if (tilesInPlay[i].selected)
				drawTile(ctx, tilesInPlay[i]);
		}	
		
		// ***** START NEW CODE *****
		// If the user is drawing a selection rectangle, draw it
		if (isSelecting) {
			drawSelectionRectangle(ctx);
		}
		// ***** END NEW CODE *****
		
		// Indicate that the canvas no longer needs to be redrawn
		redrawCanvas = false;
	}
}

As you can see on lines 20 – 23 we are testing to see if the user is drawing a selection rectangle and if they are, we are calling the drawSelectionRectangle function to draw it. Below is the drawSelectionRectangle function.

// Draws the selection rectangle
function drawSelectionRectangle(context) {
	context.strokeStyle = TILE_STROKE;

	// Figure out the top left corner of the rectangle
	var x = Math.min(selectionStartX, selectionEndX);
	var y = Math.min(selectionStartY, selectionEndY);

	// Calculate the width and height of the rectangle
	var width = Math.abs(selectionEndX - selectionStartX);
	var height = Math.abs(selectionEndY - selectionStartY);

	// Draw the rectangle
	context.strokeRect(x, y, width, height);
}	 

To draw a rectangle on an HTML5 canvas element, you first specify the border color by setting the context.strokeStyle property. If you wanted a fill color you could set the context.fillStyle as well but for our case, we only want to draw the border so we won’t set this property. Then, we call the strokeRect function on the 2d context passing in the top left x and y coordinates of the rectangle and the desired width and height.

My initial code just set the variables x and y to selectionStartX and selectionStartY respectively and that works great if the user always draws their selection rectangle by dragging their mouse down and right. But, if they draw their rectangle starting at the bottom right corner of the canvas and drag up and left, your top left coordinates will be incorrect. To remedy this we take the minimum x and y coordinates between the stored start and end values. This will guarantee that our x and y are at the top left of the rectangle.

For the same reason mentioned above, we need to take the absolute value of the difference between the start and end coordinates to figure out the width and the height. We then call context.strokeRect passing in the calculated values.

mouseUp Function
The last part of drawing the rectangle is to get it to disappear when the user lets go of the mouse. Here is our updated mouseUp function to do this.

function mouseUp(e) {
	// Code to drop the tile in the closest slot as defined in part two
	...
		
	// Deselect all tiles
	clearSelectedTiles();
	
	// ***** START NEW CODE *****
	if (isSelecting) {			
		// Reset the selection rectangle
		isSelecting = false;
		selectionStartX = 0;
		selectionStartY = 0;
		selectionEndX = 0;
		selectionEndY = 0;				
	}
	// ***** END NEW CODE *****

	needsRedraw();
}

Add the code on lines 9 – 16 to the end of the mouseUp event. Here we set the isSelecting variable to false and reset the selection rectangle coordinates if the user was drawing a selection rectangle. On the next call to the draw function, isSelecting will be false and thus the rectangle will not be drawn.

With the added code, the user can now drag a rectangle on the canvas that will resize according to the current position of the mouse. The next step is to actually select the tiles found within the rectangle after the user draws it.

Selecting the Tiles in the Rectangle

If the user is drawing a selection rectangle, when they release their mouse, we need to implement some logic to select all the tiles within the drawn rectangle. Lets add a function called selectTilesInRectangle that will carry out this logic.

// Selects all the tiles is in the user dragged rectangle
function selectTilesInRectangle() {
	
	// Get the bounds of the drawn rectangle
	var selectionTop = Math.min(selectionStartY, selectionEndY);
	var selectionBottom = Math.max(selectionStartY, selectionEndY);
	var selectionLeft = Math.min(selectionStartX, selectionEndX);
	var selectionRight = Math.max(selectionStartX, selectionEndX);

	// Loop through all the tiles and select the tile if it lies within the 
	// bounds of the rectangle
	for (var i = 0; i < tilesInPlay.length; i++) {
		var tile = tilesInPlay[i];	            

		var tileTop = tile.y;
		var tileBottom = tile.y + TILE_TOTAL_HEIGHT;
		var tileLeft = tile.x;
		var tileRight = tile.x + TILE_TOTAL_WIDTH;

		tile.selected = (tileTop >= selectionTop && tileBottom <= selectionBottom && tileLeft >= selectionLeft && tileRight <= selectionRight);	            
	}			
}

In the first part of the function we use the same logic we used in the mouseMove function to get the bounds of the rectangle. Once we have those, we loop through each of the tiles on the canvas and if the tile is completely within the bounds of the rectangle, we set the selected property to true.

Now all we have to do is make a call to this selectTilesInRectangle in the mouseUp function if the user was drawing a selection rectangle.

function mouseUp(e) {
	// Code to drop the tile into the closest slot as defined in part 2
	...
	
	if (isSelecting) {
		// ***** START NEW CODE *****
		// Mark the tiles in the drawn rectangle as selected
		selectTilesInRectangle();
		// ***** END NEW CODE *****

		// Reset the selection rectangle
		isSelecting = false;
		selectionStartX = 0;
		selectionStartY = 0;
		selectionEndX = 0;
		selectionEndY = 0;				
	}
	
	needsRedraw();
}

Since we implemented our mouseMove and mouseUp functions to loop through all the tiles and perform actions on any tile that we selected, our code for selecting and dragging multiple tiles is done! The functionality as shown in the picture at the beginning of this post is now completed.

New Overlapping Problem

Unfortunately, if you select multiple tiles and drag your mouse off of the canvas, you will see a problem that we have created. Depending on which tile you click on to drag, they might begin to overlap as you mouse moves off the canvas as shown in the picture below.

In the example above, we clicked on the ‘B’ tile to drag the selected group. Dragging is fine until we reach the right side of the canvas. When the ‘K’ tile hits the right side of the canvas, it stops as that is how we have written the code. But, the ‘B’ and ‘O’ tiles do not stop as they have not hit the side of the canvas and thus they get pushed under the ‘K’. In order to remedy this problem, we need to figure out the extremes of the selected group, the top, right, left, and bottommost tiles, and if any of those extreme tiles hit their corresponding borders of the canvas, we need to stop the entire group from moving further.

getExtremeTiles Function
First we need to add a few global variables to store the extreme tiles of the selected group.

var topmostTile;                    // Stores the topmost tile in the selected group
var bottommostTile;                 // Stores the bottommost tile in the selected group
var leftmostTile;                   // Stores the leftmost tile in the selected group
var rightmostTile;                  // Stores the rightmost tile in the selected group

Then we need to add the getExtremeTiles function.

// Finds the top, bottom, left, and rightmost tiles of the selected group
function getExtremeTiles() {	            
	for (var i = 0; i < tilesInPlay.length; i++) {
		var tile = tilesInPlay[i];
 
		if (tile.selected) {	                
			if (topmostTile == null || tile.y < topmostTile.y)
				topmostTile = tile;

			if (bottommostTile == null || tile.y > bottommostTile.y)
				bottommostTile = tile;

			if (leftmostTile == null || tile.x < leftmostTile.x)
				leftmostTile = tile;

			if (rightmostTile == null || tile.x > rightmostTile.x)
				rightmostTile = tile;
		}	           
	}
}

This function loops through all the tiles and if the tile is selected, it checks to see if it is the top, bottom, left, or rightmost tile that has been seen. By the end of the loop, we will have found the extreme tiles of the selected group.

Now we need to add a call to this function at the end of the selectTilesInRectangle function.

// Selects all the tiles is in the user dragged rectangle
function selectTilesInRectangle() {
	// Same code as defined above	
	...

	// Get the top, bottom, left, and rightmost tiles
	getExtremeTiles();
}

Modified mouseMove Function
Now that we have references to the top, bottom, left, and rightmost tiles of the selected group, we can modify the mouseMove function to ensure that no overlapping occurs when one of the extreme tiles reaches the border of the canvas. If you followed along with part two of this project you may remember that when determining whether or not to move the tile in the mouseMove function we checked to see if the tile was within the bounds of the canvas and if it was we move it, otherwise we didn’t. Now, instead of just checking the current tile, we will use our extreme tiles so that if one of the extreme tiles hits the bounds of the canvas, all selected tiles will no longer be moved in that direction.

function mouseMove(e) {
	// If the user is dragging a tile
	if (isDragging) {
		getMouse(e);

		for (var i = 0; i < tilesInPlay.length; i++) {
			var tile = tilesInPlay[i];
			
			// Only if the tile is selected do we want to drag it
			if (tile.selected) {	                   

				// Only move tiles to the right or left if the mouse is between the left and 
				// right bounds of the canvas
				if (mouseX < CANVAS_RIGHT && mouseX > CANVAS_LEFT) {

					// Move the tile if the rightmost or leftmost tile of the group is not off the canvas 
					// or if the mouse was off the canvas on the left or right side before but has now
					// come back onto the canvas
					if ((rightmostTile.x + TILE_TOTAL_WIDTH <= WIDTH && leftmostTile.x >= 0) || offX) {	                          
						tile.x = tile.x + changeInX;	                            
					}
				}	                   

				// Only move tiles up or down if the mouse is between the top and bottom
				// bounds of the canvas
				if (mouseY < CANVAS_BOTTOM && mouseY > CANVAS_TOP) {

					// Move the tile if the topmost or bottommost tile of the group is not off the canvas and the
					// or if the mouse was off the canvas on the top or bottom side before but has now
					// come back onto the canvas
					if ((topmostTile.y >= 0 && bottommostTile.y + TILE_TOTAL_HEIGHT <= HEIGHT) || offY) {	                            
						tile.y = tile.y + changeInY;
					}
				}                      
			}
		}

		// Update the variables indicating whether or not the mouse in on the canvas
		offX = (mouseX > CANVAS_RIGHT || mouseX < CANVAS_LEFT)
		offY = (mouseY > CANVAS_BOTTOM || mouseY < CANVAS_TOP)

		needsRedraw();
	}

	// Update the end coordinates of the selection rectangle
	if (isSelecting) {
		getMouse(e);

		selectionEndX = mouseX;
		selectionEndY = mouseY;	           
	
		needsRedraw();
	}
}

I have shown the entire function here but the only lines we changed were lines 19 and 31 where we replaced the tile variable with the rightmostTile, lefmostTile, topmostTile, and bottommostTile variables.

With the above code there is an occasional bug that shows up when you select multiple tiles and attempt to drag them off of the canvas. Due to the fact that the canvas element isn’t redrawing every time the mouse moves one pixel, depending on how fast you move the mouse while dragging the tiles sometimes the tiles will get stuck on the border of the canvas.

Another problem with the above implementation is that if you are dragging a line of say four tiles and you click on the farthest left tile and drag your mouse off the right border of the canvas, the moment your mouse enters the canvas again, it starts to drag the tiles instead of waiting till you get to the tile you clicked on originally as shown in the figure below (note the first image the mouse is moving to the right and the second the mouse is moving to the left).

What we want is if the user drags their mouse off of the canvas, when their mouse re-enters the canvas, the tiles won’t move until it reaches the tile the user clicked on to drag the entire group. The figure below describes the desired behavior (note the mouse is moving to the right.

In the picture, the ‘T’ tile was selected when dragging the group and when the sequence hits the right border, we no longer want to move the group to the right. Further, when the mouse comes back onto the canvas, we don’t want the group to move until the mouse reaches the x coordinate it was at when the group hit the border. The blue dashed line shows the x coordinate of the mouse when the group hits the right border and the green line marks the area we no longer want to move the group. To accomplish this we need to store the coordinates of the mouse when the group hits any of the borders of the canvas. To do this, we will create four global variables, one for each border.

var offRightX;						// Stores the x cooridnate of the mouse when a tile hits the right border
var offLeftX;						// Stores the x cooridnate of the mouse when a tile hits the left border
var offTopY;						// Stores the y cooridnate of the mouse when a tile hits the top border
var offBottomY;						// Stores the y cooridnate of the mouse when a tile hits the bottom border

Then we will initialize the variables in our init function to be coordinates that lie off of the canvas so as to not interfere with the moving of the tiles when the canvas is first rendered.

function init() {				
	// Setup the global variables
	TILE_TOTAL_WIDTH = TILE_WIDTH + TILE_RADIUS;
	TILE_TOTAL_HEIGHT = TILE_HEIGHT + TILE_RADIUS;

	canvas = document.getElementById('canvas');
	HEIGHT = canvas.height;
	WIDTH = canvas.width;
	ctx = canvas.getContext('2d');

	// Set the values of the x and y mouse coordinates used when
	// a tile hits the border to values outside of the canvas
	offRightX = -1;
	offLeftX = WIDTH + 1;
	offTopY = HEIGHT + 1;
	offBottomY = -1;
	
	...
	// The rest of the init function code as defined in part 2
}

Now we need to update our mouseMove function to take into account the stored coordinates when the selected tiles hit the borders of the canvas.

 function mouseMove(e) {
	// If the user is dragging a tile
	if (isDragging) {
		getMouse(e);

		for (var i = 0; i < tilesInPlay.length; i++) {
			var tile = tilesInPlay[i];
			
			// Only if the tile is selected do we want to drag it
			if (tile.selected) {	                   

				// Only move tiles to the right or left if the mouse is between the left and 
				// right bounds of the canvas
				if (mouseX < CANVAS_RIGHT && mouseX > CANVAS_LEFT) {

					// Move the tile if the rightmost or leftmost tile of the group is not off the canvas 
					// or if the the right or leftmost tiles hit the one of the borders previously and the
					// mouse X coordinate has now passed the stored X coordinate when the tile hit the border
					if ((rightmostTile.x + TILE_TOTAL_WIDTH <= WIDTH && leftmostTile.x >= 0) || (mouseX <= offRightX) || (mouseX >= offLeftX)) {
						tile.x = tile.x + changeInX;	                               
					}
				}	                   

				// Only move tiles up or down if the mouse is between the top and bottom
				// bounds of the canvas
				if (mouseY < CANVAS_BOTTOM && mouseY > CANVAS_TOP) {

					// Move the tile if the topmost or bottommost tile of the group is not off the canvas and the
					// or if the the top or bottommost tiles hit the one of the borders previously and the
					// mouse Y coordinate has now passed the stored Y coordinate when the tile hit the border
					if ((topmostTile.y >= 0 && bottommostTile.y + TILE_TOTAL_HEIGHT <= HEIGHT) || (mouseY <= offBottomY) || (mouseY >= offTopY)) {
						tile.y = tile.y + changeInY;
					}
				}                      
			}
		}

		// If offRightX is less than zero, meaning that the rightmostTile has not
		// hit the right border of the canvas since our last mouseMove call, and the 
		// now the rightmostTile has hit the right border, set the offRightX variable
		// to the current X coordinate of the mouse.  This will be used on the next
		// call to mouseMove to ensure the tiles are not dragged off the canvas.
		// Otherwise set offRightX to -1 indicating that the tiles are not being
		// dragged off the canvas.
		if (offRightX < 0 && (rightmostTile.x + TILE_TOTAL_WIDTH) >= WIDTH)
			offRightX = mouseX;
		else if (mouseX <= offRightX)
			offRightX = -1;

		// Same as above but for left border
		if (offLeftX > WIDTH && (leftmostTile.x <= 0))
			offLeftX = mouseX;
		else if (mouseX >= offLeftX)
			offLeftX = WIDTH + 1;

		// Same as above but for bottom border
		if (offBottomY < 0 && (bottommostTile.y + TILE_TOTAL_HEIGHT) >= HEIGHT)
			offBottomY = mouseY;
		else if (mouseY <= offBottomY)
			offBottomY = -1;
		
		// Same as above but for top border
		if (offTopY > HEIGHT && (topmostTile.y <= 0))
			offTopY = mouseY;
		else if (mouseY >= offTopY)
			offTopY = HEIGHT + 1;	                                 

		needsRedraw();
	}

	// Update the end coordinates of the selection rectangle
	if (isSelecting) {
		getMouse(e);

		selectionEndX = mouseX;
		selectionEndY = mouseY;	           
	
		needsRedraw();
	}
}

The changes are found on lines 19 and 31 again. If the program gets to these lines of code then it means the mouse is on the canvas. The extra conditions we added just say that if the current X or Y mouse coordinate have moved past the X or Y coordinate we stored when the tiles hit the border of the canvas, then start moving the tiles. If the mouse hasn’t hit that coordinate yet, or it is in the area marked by the green line in the image above, then don’t move the tiles.

In lines 45 to 66 we update the coordinates we store if the tiles have hit one of the borders.

Unfortunately it requires quite a bit of code to remedy this problem but I don’t see any other way to do it as you have to store the coordinates for each border. If the described problems are not something that bothers you then you can remove the above added code from the mouseMove function and just stick with what we had before.

Modified mouseDown Function
Now that we are using the extreme tile references in our mouseMove function, if the user selects and drags a single tile, we need to set that tile to be the top, bottom, left, and rightmost tiles. To do this, add lines 8 – 11 in the code below to the mouseDown function

function mouseDown(e) {
	...
		// Determine if the tile was clicked
		if (mouseX >= left && mouseX <= right && mouseY >= top && mouseY <= bottom) {																									
			// ***** START NEW CODE
			// If the user selected a tile that was not selected before,
			// clear all selected tiles
			if (!tilesInPlay[i].selected) {
				clearSelectedTiles();
				topmostTile = bottommostTile = leftmostTile = rightmostTile = tilesInPlay[i];
			}
			// ***** END NEW CODE *****

			// Indicate that the current tile is selected
			tilesInPlay[i].selected = true;			
			isDragging = true;

			...
		}
	...
}

Lastly, we need to clear the extreme tile variables whenever we clear the selected tiles. Add the following code to the clearSelectedTiles function.

// Sets the tile.selected property to false for
// all tiles in play and clears all extreme tiles
function clearSelectedTiles() {
	for (var i = 0; i < tilesInPlay.length; i++) {
		tilesInPlay[i].selected = false;
	}

	// ***** START NEW CODE *****	
	// Clear the exterme tiles
	topmostTile = null;
	bottommostTile = null;
	leftmostTile = null;
	rightmostTile = null;
	// ***** END NEW CODE *****
}

To recap, we have now added the ability for the user to select multiple tiles by dragging a dynamically drawn rectangle around the desired tiles and we have also prevented the tiles from stacking on top of each other when dragging multiple tiles.

he entire source code for the project up to this point can be downloaded here.

Follow

Get every new post delivered to your Inbox.

Join 69 other followers