Visual Studio Extension: Browse Current Project

March 30, 2011

I’ve just published my first Visual Studio Extension. It is a very simple one-command extension that allows you to view the contents of the folder that contains the current project in the Visual Studio Browser. At the moment, only Visual Studio 2010 is supported.

Instructions for Use

To view open the browser on the project’s folder, just click on the button in the Solution Explorer’s toolbar. The button is only visible when you have a solution loaded in Visual Studio.

The contents of the folder that contains the current project will be displayed in Visual Studio. That’s it!

Advertisements

SQL Server: Drop all Foreign Keys that Reference a Table

March 17, 2011

This post has moved to Yagni.net.

 


BBC Micro High Scores!

May 11, 2010

Pengo 46330

Labyrinth 63870

Monsters 10620

Snapper 41820

Air Lift 70460

Zalaga 59490

Pole Position 56600

Nemesis 16290

Space Eagles 11880

Starship Command 889

Chucky Egg 235330

Snake (fast) 1016

Planetoid 115400

Mr EE 103100


Preventing Users Navigating Away from an AJAX.Net Page without Saving their Changes

December 11, 2009

On ASP.Net / AJAX recent project I was asked to ensure that users cannot navigate away from a web form if they have made changes that have not yet been submitted.

Requirements

The requirements were:

  • Enable users to submit the form by clicking the “Save” or “Cancel” buttons.
  • When user navigates away using any other method, show a dialog that states that changes will be lost if the user continues. These methods to include:
    • Any other controls on the page
    • Back button
    • Refresh button
    • Typing another address in the address bar
    • Picking another page from history
    • Closing the web browser
    • Anything else
  • Only show the dialog if the form is “dirty” (ie. there are unsaved changes”
  • Work with IE6 (I know, I know).
  • The solution should work client-side, so must be written in JavaScript.

Note that his solution has not been tested with other browsers.

Preventing Users Navigating

When a user attempts to navigate using any of the methods above, the window.onbeforeunload event will fire. We can use this event to pop-up a dialog that warns the user that she has not submitted her changes, and asks her to confirm that she wants to continue.

Checking for Dirt

In order to check for dirtiness, we must compare the original value of all the controls on the page with their value when they are submitted. A number of sources suggest that the browser maintains the default value for controls, and that we can compare the current value with that default value in the onbeforeunload event handler. However, there are several issues with this approach:

  • The default value is generally reset following an AJAX postback. This means that we must preserve the control’s original values of the controls when they first load, so gain nothing from using the browser’s method for tracking value changes.
  • In IE6, the defaultSelected property of HTML OPTION controls is, by default, set to true for all items in a drop-down list. The ASP.Net list control appears to set this value explicitly, I didn’t want to have to do so on the equivalent HTML control. As a result of the default, however, it is often impossible to determine if the user has unselected an item or if they have simply not selected the item. As a result it is best to track changes to the selection of options ourselves, rather than using the browser’s faulty tracking mechanism.

Allowing Certain Controls to Submit without Warning

It is important that some controls are able to submit the form without the using being warned that changes have been made. Typically, these controls include a button that allows the user to save her changes, and possibly one to exit without saving.

In order to implement these, we set a flag that indicates that a dirtiness check is required, then use the client-side click event of the buttons to reset the flag so that the check isn’t performed when these buttons are clicked.

OnBeforeUnload Sometimes Fires Twice

There is a bug in IE6 that means that, under some circumstances, the onbeforeunload event fires twice. We don’t want the confirm dialog to be displayed twice when the user attempts to leave a page, so we need to take measures. It is easy to set a flag the first time the onbeforeunload event fires that indicates that we can ignore it the second time. The slight complication is that the user may click “cancel” to prevent the navigation when the dialog first appears. If that happens, we don’t want to suppress the confirmation dialog for subsequent attempts to navigate away from the page. The solution is to start a timer that will reset the flag if the user remains on the page, thus allowing the dialog to appear when the user attempts to navigate again.

The Code

Here’s the JavaScript that I use:


<script type="text/javascript">
<!--

// Function to maintain original default states for all fields.
//  In order to test for dirtiness, we will be checking if the default
//  value for each control matches its current value. However, this
//  default is not normally preserved across partial postbacks. We need to
//  preserve these values ourselves.
function keepDefaults(form) {

// Get a reference to the form (in ASP.Net there should only be the one).
var form = document.forms[0];

// If no original values are yet preserved...
if (typeof (document.originalValues) == "undefined") {

// Create somewhere to store the values.
document.originalValues = new Array();

}

// For each of the fields on the page...
for (var i = 0; i < form.elements.length; i++) {

// Get a ref to the field.
var field = form.elements[i];

// Depending on the type of the field...
switch (field.type) {

// For simple value elements...
case "text":
case "file":
case "password":
case "textarea":

// If we don't yet know the original value...
if (typeof (document.originalValues[field.id]) == "undefined") {

// Save it for later.
document.originalValues[field.id] = field.value;

}
break;

// For checkable elements...
case "checkbox":
case "radio":

// If we don't yet know the original check state...
if (typeof (document.originalValues[field.id]) == "undefined") {

// Save it for later.
document.originalValues[field.id] = field.checked;

}
break;

// For selection elements...
case "select-multiple":
case "select-one":

// The form is dirty if the selection has changed.

// For each of the options...
var options = field.options;
for (var j = 0; j < options.length; j++) {

var optId = field.id + "_" + j;

// If we don't yet know the original selection state...
if (typeof (document.originalValues[optId]) == "undefined") {

// Save it for later.
document.originalValues[optId] = options[j].selected;

}
}
break;
}

}
}

// Call function to preserve defaults every time the page is loaded (or is
// posted back).
Sys.Application.add_load(keepDefaults);

// Assume that a check for dirtiness is required.
//  If this value is still true, we will check for dirtiness when the page
//  unloads.
var dirtyCheckNeeded = true;

// Function to flag that a check for dirtiness is not required.
//  Called by Save and Cancel buttons to indicate that a dirty check is
//  not actually required.
function ignoreDirty() {
dirtyCheckNeeded = false;
}

// Function to check if the page is dirty.
//  The function compares the default value for the control (the one it
//  was given when the page loaded) with its current value.
function isDirty(form) {

// For each of the fields on the page...
for (var i = 0; i < form.elements.length; i++) {
var field = form.elements[i];

// Depending on the type of the field...
switch (field.type) {

// For simple value elements...
case "text":
case "file":
case "password":
case "textarea":

// The form is dirty if the value has changed.
if (field.value != document.originalValues[field.id]) {
// Uncomment the next line for debugging.
//alert(field.type + ' ' + field.id + ' ' + field.value + ' ' + document.originalValues[field.id]);
return true;
}
break;

// For checkable elements...
case "checkbox":
case "radio":

// The form is dirty if the check has changed.
if (field.checked != document.originalValues[field.id]) {
// Uncomment the next line for debugging.
//alert(field.type + ' ' + field.id + ' ' + field.checked + ' ' + document.originalValues[field.id]);
return true;
}
break;

// For selection elements...
case "select-multiple":
case "select-one":

// The form is dirty if the selection has changed.
var options = field.options;
for (var j = 0; j < options.length; j++) {
var optId = field.id + "_" + j;
if (options[j].selected != document.originalValues[optId]) {

// Uncomment the next line for debugging.
//alert(field.type + ' ' + field.id + ' ' + options[j].text + ' ' + options[j].selected + ' ' + document.originalValues[optId]);
return true;
}
}
break;
}
}

// The form is not dirty.
return false;
}

// Clicking on some controls in (at least) IE6 caused the onbeforeunload
// to fire *twice*. We use this flag to check for this condition.
var onBeforeUnloadFired = false;

// Function to reset the above flag.
function resetOnBeforeUnloadFired() {
onBeforeUnloadFired = false;
}

// Handle the beforeunload event of the page.
//  This will be called when the user navigates away from the page using
//  controls on the page or browser navigation (back, refresh, history,
//  close etc.). It is not called for partial post-backs.
function doBeforeUnload() {

// If this function has not been run before...
if (!onBeforeUnloadFired) {

// Prevent this function from being run twice in succession.
onBeforeUnloadFired = true;

// If the dirty check is required...
if (dirtyCheckNeeded) {

// If the form is dirty...
if (isDirty(document.forms[0])) {

// Ask the user if she is sure she wants to continue.
event.returnValue = "If you continue you will lose any changes that you have made to this record.";

}
}
}

// If the user clicks cancel, allow the onbeforeunload function to run again.
window.setTimeout("resetOnBeforeUnloadFired()", 1000);
}

// Hook the beforeunload event of the page.
//  Call the dirty check when the page unloads.
if (window.body) {
// IE
window.body.onbeforeunload = doBeforeUnload;
}
else
// FX
window.onbeforeunload = doBeforeUnload;

// -->
</script>

The source code for my Save and Cancel buttons looks like this:


<asp:Button ID="btnEdit" runat="server" Text="Save" OnClick="btnEdit_Click" OnClientClick="ignoreDirty();" />
<asp:Button ID="btnCancel" runat="server" Text="Cancel" CssClass="btn btnCancel" OnClick="btnCancel_Click" OnClientClick="ignoreDirty();" CausesValidation="false" />

References

I looked at a range of solutions before developing the one I present here, including:


A Good IT Solution is Like a Good Theory

December 7, 2009

What makes a good IT solution?

Theory = “A coherent group of general propositions used as principles of explanation for a class of phenomena.” Random House Dictionary

Consider the analogy, Solution = Theory.

T: Explains a whole class of phenomena.

S: Solves a whole class of problems.

T: Predicts results that have not yet been discovered.

S: Highlights and solves problems that have not yet been defined.

T: Does not conflict with existing results.

S: Does not break existing solutions.

T: Parsimony: Everything else being equal, the simplest theory is to be preferred.

S: As simple as possible, but no simpler.

T: No one theory explains everything.

S: No one solution provides a universal panacea.

T: Models changing relationships between observed data over time

S: Models changing relationships between input data over time

References


Some Basic Interview Advice

November 6, 2009

Notes from a recent training session (rather a basic one – but fundamentals are easily forgotten in stressful situations) :

Advance Preparation

Keep a copy of the job advert and spec

Keep a copy of your application form / CV

Research the department / organisation + make notes

  • Understand where the organisation is going

Prepare questions to ask the interviewers

On-the-Day Preparation

Re-read your key documents – your application form / CV etc.

General Tips

Be:

  • Punctual
  • Smart
  • Polite
  • Natural but not casual

During the Interview

  • Take your time
  • Ask for repetition / clarification of questions – either because you need them or to give you time to answer
  • Give brief answers – ask if more detail is required.
  • Answer questions

The Fold

November 5, 2009

There has been a long running debate amongst web designers as to whether or nor “the fold” matters, or even exists. Here are my thoughts:

  1. Of *course* users know how to scroll. Just like they know how to unfold physical media. But knowing how is not the same as doing.
  2. Everything else being equal, people are less likely to do things that take more effort. Scrolling takes effort (not much, but some), so users are more likely to read things above the fold than below it.
  3. There are actually two folds – the first is at the bottom of your browser window. The second is in your head. It is the amount of scrolling + reading *you* are willing to do before you give up on a web page. Some people who visit this page will not read this sentence – it will be below their personal fold.
  4. Everything else being equal, users will do things that they typically find most rewarding. If web-designers believe in the fold, they will put the content they consider most important near the top of the page. As a result, we actually train users not to scroll – thus re-reinforcing the idea of the existence of the fold.
  5. Users develop strategies for getting what they want from the web. These strategies vary from person to person and from day to day. The way I/you look at a page might not be typical. You can’t always generalise your own experience.
  6. Without watching what real users actually do, we’re just guessing about the whole fold issue. (Although some of our guesses are more educated than others).