Category Archives: 6429

Detecting User Regional Settings In The Web Browser

Recently, a friend of mine asked me something like: “How do I get the user’s regional settings for a request to a web server?”

As far as I know, web browser only send an Accept-Language HTTP header and nothing more. You can take this and use the default regional settings for that language but, if your user is anything like me, you’ll be wrong.

So, what’s the problem of getting it wrong?

If you are just generating HTML and keep it consistent, nothing’s wrong. But what if your user wants to copy some numeric and/or date values to, say, Excel? Or if you want to export some data as a CSV file?

A solution

Going through the JScript Language Reference, I found that both Number and Date have locale related toString methods and I started playing with them.

Numeric format settings

To the numeric format settings, first we need a number that will behave in a predictable manner in any culture (“any culture” means “any culture I know”) and for all possible settings and convert it to a string using the toLocaleString method:

var number = 111111111.111111111;
var numberString = number.toLocaleString();


(With my regional settings, numberString becomes 111 111 111.11)



To get the decimal separator, all it takes is getting the first non 1 from the end:



var decimalSeparator;
var decimalDigits;
for (var i = numberString.length - 1; i >= 0; i--) {
    var char = numberString.charAt(i);
    if (char != "1") {
        decimalSeparator = char;
        decimalDigits = numberString.length - i - 1;
        break;
    }
}


And if you count how many 1s were skipped, we get the number of decimal digits.



In a similar way, the first non 1 will be the digit grouping separator:



var groupSeparator;
for (var i = 0; i < numberString.length; i++) {
    var char = numberString.charAt(i);
    if (char != "1") {
        groupSeparator = char;
        break;
    }
}


Now that we have the digit grouping separator, we can get the digit groups (these groups might not all have the same size):



var digitGrouping = numberString.substring(0, numberString.length - decimalDigits - 1).split(groupSeparator);
for (g in digitGrouping) {
    digitGrouping[g] = digitGrouping[g].length;
}


Date and time settings


Date and time values are more difficult to parse and you might not need all information. So, I’ll just get the value and let the parsing to you:



var dateTime = new Date(9999, 11, 31, 23, 30, 45);
dateTimeString = dateTime.toLocaleString();


List settings


The last setting is the list separator (very useful for those CSV files):



var list = ["a", "b"];
listSeparator = list.toLocaleString().substring(1, 2);


Test page



Here is a test page the get all these settings:



<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
    <title>Test Page</title>
    <style type="text/css">
        label  { width: 8em; text-align: right; padding-right: 0.5em; white-space: nowrap; }
        span { border: 1px solid; white-space: nowrap; }
    </style>
    <script type="text/javascript">
        function init() {
            document.all.userLanguage.innerText = window.navigator.userLanguage;
            document.all.systemLanguage.innerText = window.navigator.systemLanguage;

            // Decimal separator and decimal digits
            var number = 111111111.111111111;
            var numberString = (111111111.111111111).toLocaleString();

            var decimalSeparator;
            var decimalDigits;
            for (var i = numberString.length - 1; i >= 0; i--) {
                var char = numberString.charAt(i);
                if (char != "1") {
                    decimalSeparator = char;
                    decimalDigits = numberString.length - i - 1;
                    break;
                }
            }
            document.all.decimalSeparator.innerText = decimalSeparator;
            document.all.decimalDigits.innerText = decimalDigits;

            // Digit grouping separator and digit goups
            var groupSeparator;
            for (var i = 0; i < numberString.length; i++) {
                var char = numberString.charAt(i);
                if (char != "1") {
                    groupSeparator = char;
                    break;
                }
            }
            document.all.groupSeparator.innerText = groupSeparator;

            var digitGrouping = numberString.substring(0, numberString.length - decimalDigits - 1).split(groupSeparator);
            for (g in digitGrouping) {
                digitGrouping[g] = digitGrouping[g].length;
            }
            document.all.digitGrouping.innerText = digitGrouping.toString();

            // Date format
            var dateTime = new Date(9999, 11, 31, 23, 30, 45);
            var dateTimeString = dateTime.toLocaleString();
            document.all.dateTimeFormat.innerText = dateTimeString;

            // List separator
            var list = ["a", "b"];
            var listSeparator = list.toLocaleString().substring(1, 2);
            document.all.listSeparator.innerText = listSeparator;
        }
    </script>
</head>
<body onload="init()">
    <p><label for="userLanguage">User language:</label><span id="userLanguage"></span></p>
    <p><label for="systemLanguage">System language:</label><span id="systemLanguage"></span></p>
    <p><label for="decimalSeparator">Decimal separator:</label><span id="decimalSeparator"></span></p>
    <p><label for="decimalDigits">Decimal digits:</label><span id="decimalDigits"></span></p>
    <p><label for="groupSeparator">Digit separator:</label><span id="groupSeparator"></span></p>
    <p><label for="digitGrouping">Digit grouping:</label><span id="digitGrouping"></span></p>
    <p><label for="dateTimeFormat">Date/Time format:</label><span id="dateTimeFormat"></span></p>
    <p><label for="listSeparator">List separator:</label><span id="listSeparator"></span></p>
</body>
</html>

More On ASP.NET Validators And Validation Summary Rendering of Properties

On previous posts [^][^] I mentioned the size of ASP.NET validators and validation summary rendering and the fact that expando attributes are being used to add properties. Mohamed also mentions this issue.


Besides the fact that custom attributes aren’t XHTML conformant, Firefox differs from Internet Explorer in the way it handles these attributes.


On Internet Explorer, these attributes are converted in string properties of the HTML element. On Firefox, on the other hand, these attributes are only accessible through the attributes collection.


I wonder why I don’t like client-side JavaScript development.

The Cause Of ASP.NET Validators And Validation Summary Slowness

When building ASP.NET pages, if you use too many validators and validation summaries your pages can become very slow. Have you ever wondered why?


Lets build a simple page web page with a few validators. Something like this:


Web page with validation


The page is composed of:



ASP.NET renders the ValidationSummary as a DIV and each validator as a SPAN and uses expando attributes to add properties to those elements.


According to the documentation, expando attributes are set dynamically from JavaScript to preserve XHTML compatibility for the rendered control’s markup.


The problem is that all that JavaScript makes the HTML document larger and slower to execute than if the properties were rendered in HTML as attributes of the elements.


For such a small page, the difference in size approaches 2k bytes. If you add a few dozen validators to he page, the slowness is noticeable.


I’m all in favor of strict standards and standards compliance, but in this case, I wish XHTML would allow arbitrary attributes.

How To Close Browser Windows In Windows Internet Explorer 7

When a web page uses scripting to close a browser window that was opened by the user and not opened by some action on another page, Internet Explorer pops up a question to the user warning that “The webpage you are viewing is trying to close the tab.” (in this case, Internet Explorer 7) and asking the user for permission to close the tab.

Before Internet Explorer 7, all that was needed to do was setting the window.opener property to a non null value:

window.opener = self;
window.close();

Unfortunately, Internet Explorer 7 isn’t fooled by this. Internet Explorer 7 knows if the window was opened by the user or not, regardless the value of the window.opener property.


Fortunately, Internet Explorer can still be fooled:


window.open("","_self");
window.close();

Going one step further, if you want all your calls to the window.close method to work this way, you can change the method implementation like in the following example:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>Untitled Page</title>
    <script type="text/javascript">
    // Save a reference to the original method.
    var windowClose = window.close;

    // Re-implement window.open
    window.close = function ()
    {
        window.open("","_self");
        windowClose();
    }
    </script>
</head>
<body>
<input type="button" value="Close Me!" onclick="window.close()" />
<input type="button" value="Close Me!" onclick="windowClose()" />
</body>
</html>