Checking if any control in view has changed in ASP.NET

The typical way to check for whether anything has changed in the view when you’re navigating away from a page is to bind a method to the change event of each input and to set a flag if this has happened. This flag is then checked when leaving the page and a notification is shown to the user if the flag is raised. This is all good but it does not take into account things like changing the order of inputs and changing values back to their original values. Also, creating dynamic elements makes this way of doing things a bit tricky because you have to add registering of the handlers to each method which adds new elements.

Just recently I had to implement this kind of checking. The implementation makes use of jQuery and has been tested with ASP.NET MVC but I don’t see any reason why it should not work with other platforms as well (at least with some minor changes). So without further ado, here’s the code.

/**
The variable contains all the values of the new as found by the previous call 
to storeViewValues.
*/
var viewValues = [];

/**
Set this to true to enable dirty-checking for the current page.

This is also set when registering change checking for the view.
*/
var enableDirtyCheck = false;

/**
Collects all view values and returns the array.
*/
function collectViewValues() {
    var newViewValues = [];

    // Store inputs.
    $('input:enabled').each(function () {
        if ($(this).attr('id') != null && $(this).attr('id') != '') {
            // Ignore the ones that have '.' in their IDs. These are not part 
            // of our input values, since they are not allowed by
            // MVC when generating inputs.
            var id = $(this).attr('id');
            if (id.indexOf('.') < 0)
                newViewValues.push({ selector: 'input', id: $(this).attr('id'),
                value: $(this).val() });
        }
    });

    // Store select values.
    $('select:enabled').each(function () {
        var that = this;
        if ($(this).attr('id') != null && $(this).attr('id') != '') {
            $(this).find('option:selected').each(function () {
                // Ignore the ones that have '.' in their IDs. These are not
                // part of our input values, since they are not allowed by
                // MVC when generating inputs.
                var id = $(that).attr('id');
                if (id.indexOf('.') < 0)
                    newViewValues.push({ selector: 'select', id:
                       $(that).attr('id'), value: $(this).val() });
            });
        }
    });
    return newViewValues;
}

/**
Stores the current view input and select values to viewValues.
*/
function storeViewValues() {
    viewValues = collectViewValues();
}

/**
Checks for changes in the view.
You must call storeViewValues when the view is fully loaded before calling 
this method.
*/
function checkChanges() {
    var hasChanged = false;

    if (enableDirtyCheck) {
        // Collect current input values.
        var newViewValues = collectViewValues();

        if (newViewValues.length != viewValues.length) {
            hasChanged = true;
        }
        else {
            // Check values.
            for (var i = 0; i < newViewValues.length; i++) {
                var newViewValue = newViewValues[i];
                var viewValue = viewValues[i];

                if (newViewValue.id != viewValue.id) {
                    hasChanged = true;
                    break;
                }

                if (newViewValue.selector != viewValue.selector) {
                    hasChanged = true;
                    break;
                }

                if (newViewValue.value != viewValue.value) {
                    hasChanged = true;
                    break;
                }
            }
        }
    }

    if (hasChanged)
        return "The page contains modifications!";
    return null;
}

/**
Call this method at the end of the current view if you wish to enable dirty
checking for the view.
*/
function registerDirtyChecking() {
    $(document).ready(function () {
        // Default to checking whether the view is dirty or not.
        enableDirtyCheck = true;

        // Store existing values.
        storeViewValues();

        window.onbeforeunload = function (e) {
            var ret = checkChanges();
            if (ret != null) {
                e.returnValue = ret;
                return ret;
            }
        };

        $('form').submit(function () {
            enableDirtyCheck = false;
        });
    });
}

The implementation takes into account that Internet Explorer shows the message even when you return null from the onbeforeunload handler, which actually works on Chrome, for instance. I also added setting of enableDirtyCheck to false when submitting the form, to ignore the message when we’re actually submitting the changes.

To use the code, simply include the bit of code in your view and call registerDirtyChecking at the end of the view. This forces the document ready handler to be added as the last one to be called (if other document ready handlers don’t add new document ready handlers themselves, that is).

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s