Javascript minifying

I’ve just been integrating the MVC ScriptManager for combining scripts and minifying them for performance and I came across an oddity with the minifier and javascript.  The problem is caused by the following snippet of code,

cssHandler : {
    position: "relative",
    /*top:"2px",*/
    right:"-3px",
    float:"right",
    /*width:"0px",*/
    borderRight:"1px solid #fff",
    borderLeft:"1px solid #fff",
    height:"20px",
    cursor:"col-resize"
    },

The float is a reserved word and the minifier choked on it.  Technically the code works so I dug a little deeper.  The minifier being used was the .net version of the YUI Compressor and after a dig into the source I found the reservedKeywordAsIdentifier property.  If I tweak the JavaScriptCompressor.Parse function to call setReservedKeywordAsIdentifier(true) on the compilerEnvirons object before the Parser object is created it compresses the javascript correctly.

A search for the expected behaviour of reserved words as identifiers turned up this article from 2002 by Douglas Crockford.  In it he suggests that javascript has a lame restriction that means identifiers are not allowed as member names (I’m paraphrasing).  That partially explains the way the library is setup. At least some versions of javascript wouldn’t like that bit of code.

The actual javascript parsing stuff appears to come from the Mozilla Rhino project.  The one thing I can’t figure out is how I’m supposed to configure the parser correctly for a particular version of javascript.

On balance though I figure the simplest solution is to simply change the original code so that I use a string rather than a bare word and that will bypass the whole set of problems.

Anyway, if you come across the Exception ‘System.InvalidOperationException: invalid property id’ when using the compressor with the MVC ScriptManager that might be the problem.  The rest of the stack trace starts like this,

  at Yahoo.Yui.Compressor.CustomErrorReporter.Error(String message, String sourceName, Int32 line, String lineSource, Int32 lineOffset)
   at EcmaScript.NET.Parser.AddError(String messageId)
   at EcmaScript.NET.Parser.ReportError(String messageId)
   at EcmaScript.NET.Parser.primaryExpr()

I’m hoping that the next time I come across this problem I’ll find my own post with google!

Advertisements

ASP.Net MVC localizable GET parameters

I’ve now written a solution to my date localization problem in MVC 1.0.  It looks like MVC 2.0 will require a different implementation because they’ve changed that logic.  It looks like it’s mostly for the better.

Essentially I’ve added a hidden value with the name ‘hl’ onto my GET forms with the System.Globalization.CultureInfo.CurrentCulture.LCID so that I know which culture to use when parsing the values.

I’ve then created my own version of the ValueProviderDictionary and within our own Controller base class we set that after the Initialize is called.  That ValueProviderDictionary is identical except that before the query parameters are processed I see if there is a the culture value on the query and read that and use it instead of the Invariant culture if possible.

I’m only doing the query parameters at the moment since I’m not sure if it’s a good idea to the route values.


    NameValueCollection queryString = ControllerContext.HttpContext.Request.QueryString;
    if (queryString != null) 
    {
        CultureInfo getCulture = ReadCulture(invariantCulture, queryString);
        string[] keys = queryString.AllKeys;
        foreach (string key in keys) {
            string[] rawValue = queryString.GetValues(key);
            string attemptedValue = queryString[key];
            ValueProviderResult result = new ValueProviderResult(rawValue, attemptedValue, getCulture);
            AddToDictionaryIfNotPresent(key, result);
        }
    }

private static CultureInfo ReadCulture(CultureInfo invariantCulture, NameValueCollection queryString)
{
    CultureInfo getCulture = invariantCulture;
    if (!String.IsNullOrEmpty(queryString["hl"]))
    {
        string host_language = queryString["hl"]; // host language
        try
        {
            int language;
            if (Int32.TryParse(host_language, out language))
                getCulture = CultureInfo.GetCultureInfo(language);
        }
        catch (Exception)
        {
            Errors.Log.WarnFormat("Encountered unexpected culture {0}", host_language);
        }
    }
    return getCulture;
}

Open your mouth and prove it….

Shortly after writing that post on the ‘weird’ MVC decision I just figured out the reasoning behind the invariant culture for the other types of parameter.  The problem with the other types is that they come from url’s.  People tend to pass url’s around and you generally want them to work consistently no matter who’s loading them, wherever they are.  That explains their reasoning.

Now all I need to do is solve my problem.  I want the user to be able to enter a search date using their own date format and hit search and for that to be reflected in the url so that they can then bookmark the search.  Pretty simple, except that the form doesn’t see that text as a date, it sees it as text and doesn’t convert it to an invariant.

The only thing that I can think of is to store the culture the search was made with on the form so that it’s embedded into the url allowing me to have the best of both worlds.  Urls that are shareable and bookmarkable and pages that are aware of your local preferences for formatting.

MVC internationalization/localization weirdness

I’ve just encountered a weird decision in the ASP.Net MVC framework that I don’t understand.  When it comes to interpreting parameters especially complex ones being done via ModelBinding it will assign the parameters different cultures depending on where the parameters came from.  I’m kind of wondering why that’s the case.

The specific reason I now know this, and have fudged my code to get around that is that I’m passing dates on the query string using a form with a GET.  Since I’m in little england my date got read the wrong way and I kept getting my month and day reversed. Previously the code I’d written that was attached to a form that did a POST worked fine, and continued to while I was pulling my hair out wondering why my new code was broken.

The part that appears to be responsible for this is the ValueProviderDictionary and there we find this comment.

// We use this order of precedence to populate the dictionary:
// 1. Request form submission (should be culture-aware)
// 2. Values from the RouteData (could be from the typed-in URL or from the route's default values)
// 3. URI query string

As I’ve implemented my own date model binder like Scott Hanselman I’ve put this fudge into it,

// KLUDGE: force the culture since for some reason the MVC platform only uses the
// users culture if the results come from a form, not if they are from a route or query string.
if (valueResult.Culture == CultureInfo.InvariantCulture)
{
    valueResult = new ValueProviderResult(valueResult.RawValue, valueResult.AttemptedValue,
                                          System.Threading.Thread.CurrentThread.CurrentUICulture);
}
return (T?)valueResult.ConvertTo(typeof(T));

It’s nuts but it works around my problem.  There may be a better way to hook into the framework but since I don’t really understand their reasoning for doing that I’ve decided to leave it alone for now.