See Joel Program

June 10, 2008

Update: Fixing Sys.Application.initialize

Filed under: AJAX, ASP.NET AJAX, JavaScript — Joel Rumerman @ 5:41 pm

See this post for the latest solution. This one has a small problem with it.

Yesterday I posted an entry where I wrote about an initialization problem in the AJAX Library portion of ASP.NET AJAX. Today, having a few spare minutes and already having found a few entries on the setTimeout problem which I linked to yesterday, I decided to do a bit more research on the window.onload problem and see how other libraries had gotten around the issue. I started with JQuery because it’s next on my list of AJAX libraries to become familiar with. So I downloaded the library, opened Visual Studio, and took a look around (don’t you love the open source nature of JS?!).

What I found started me down a path of getting kind of annoyed at Microsoft and coming up with a good solution for the Sys.Application.initialize problem.

Here’s the excerpt from JQuery that is responsible for notifying the system that the browser is ready to be modified.

// Mozilla, Opera (see further below for it) and webkit nightlies currently support this event
if ( document.addEventListener && !jQuery.browser.opera)
    // Use the handy event callback
    document.addEventListener( "DOMContentLoaded", jQuery.ready, false );

// If IE is used and is not in a frame
// Continually check to see if the document is ready
if ( jQuery.browser.msie && window == top ) (function(){
    if (jQuery.isReady) return;
    try {
        // If IE is used, use the trick by Diego Perini
        // http://javascript.nwbox.com/IEContentLoaded/
        document.documentElement.doScroll("left");
    } catch( error ) {
        setTimeout( arguments.callee, 0 );
        return;
    }
    // and execute any waiting functions
    jQuery.ready();
})();

Nothing too fancy here, but what got me was two things. First, if the browser supported the DOMContentLoaded event, they respected it. Seems simple enough, why didn’t Microsoft do that? Second, they re-used somebody else’s proven code to handle browsers that don’t support the DOMConentLoaded event (e.g. IE). Because the code had a link it I followed the trail, which brought me to Diego Perini’s site where he listed the original concept and explained how it worked. BUT, from there, he linked to four other people that had written other ways (ok, there’s some repeat among them) of getting around the missing DOMContentLoaded event, some of which were newer and some better than his.

From there, I traveled to other blog entries listing other ways of getting around it with development efforts on going.

So, after I perused the web for 20 minutes I had 3 valid alternatives to using the setTimeout method that Microsoft uses in the AJAX Library.

IMHO, the best total solution has been crafted by the venerable Dean Edwards who pulled concepts from the solution engineered by Matthias Miller. Another possibility is this one, which is Diego Perini’s that is used by JQuery.

Given this information, I figured it’s probably pretty easy to fix Sys.Application.initialize, and also wonder why MSFT decided to come up with their own way. I’m trying to look at the timeline and I guess MSFT had written too much of ASP.NET AJAX before this problem was solved correctly? Plus, they were trying to support as many browser’s as possible and the solution I’ll cover now doesn’t support any legacy browsers such that everything executes when the window’s load event fires. Hopefully, MSFT will adopt something like the following solution for their Sys.Application.initialize problem.

So here’s my solution to Sys.Application’s initialize problem. I’m sure it’s not perfect and may not support all browsers (I’m testing Windows Safari 3.1.1, IE 7, FF 3.0 RC2, FF 2.0, and Opera 9.0), but it’s pretty much re-used directly from Dean’s code and he’s a pretty smart guy. If a browser isn’t supported, I fall back to using window.onload automatically to execute the _doInitialize method when the window loads. I can’t claim (and don’t want any) rights  to this code since I gleaned most of it from Dean’s site so if you want to use this, go right ahead.

Here’s the new body of Sys.Application.initialize.

function Sys$_Application$initialize() {
    if(!this._initialized && !this._initializing) {
        this._initializing = true;
        var loadMethodSet = false;
        var initializeDelegate = Function.createDelegate(this, 
          this._doInitialize);
        if (document.addEventListener) {
            loadMethodSet = true;
            document.addEventListener("DOMContentLoaded",
                     initializeDelegate, false);
        }
        if (/WebKit/i.test(navigator.userAgent))
        {
            loadMethodSet = true;
            this._load_timer = setInterval(function()
            {
                if (/loaded|complete/.test(document.readyState))
                {
                    initializeDelegate();
                }
            }, 10);
        }
        else
        {
            /*@cc_on @*/
            /*@if (@_win32)
            loadMethodSet = true;
            document.write("<script id=__ie_onload defer src=//0>
                            <\/scr"+"ipt>");
            var deferScript = document.getElementById("__ie_onload");
            if (deferScript) {
                deferScript.onreadystatechange = function() {
                    if (this.readyState == "complete") {
                        initializeDelegate();
                    }
                };
            }
            /*@end @*/
        }

        // only if no other method will execute initializeDelegate is
        // it wired to the window's load method.
        if (!loadMethodSet)
        {
            $addHandler(window, "load", initializeDelegate);
        }
    }
}

Another necessary change is clearing the interval created by the Safari creator if it exists. We do this in the _doInitialize method. Doing it inside the if statement caused the initializeDelegate to execute twice. I’m not sure why, but it’s a simple fix.

function Sys$_Application$_doInitialize() {
       if (this._load_timer !== null)
       {
           clearInterval(this._load_timer);
           this._load_timer = null;
       }
       Sys._Application.callBaseMethod(this, 'initialize');

Of course, we attach the _load_timer to Sys._Application.

Sys._Application = function Sys$_Application() {
    Sys._Application.initializeBase(this);
    this._load_timer = null;
    this._disposableObjects = [];
  ...

Finally, if the user doesn’t use ScriptManager to register the Sys.Application.initialize method, we need to automatically call _doInitialize rather than initialize as is done currently. So the _loadHandler method goes from

function Sys$_Application$_loadHandler() {
    if(this._loadHandlerDelegate) {
        Sys.UI.DomEvent.removeHandler(window, "load", 
            this._loadHandlerDelegate);
        this._loadHandlerDelegate = null;
    }
    this.initialize();
}

to

function Sys$_Application$_loadHandler() {
    if(this._loadHandlerDelegate) {
        Sys.UI.DomEvent.removeHandler(window, "load", 
          this._loadHandlerDelegate);
        this._loadHandlerDelegate = null;
    }
    this._initializing = true;
    this._doInitialize();
}

That’s about it. I’m going to test this is a large scale application tomorrow and see how it does, but it passes all of my tests at the moment.

17 Comments »

  1. Hi,
    I think I have the same problem. Where are these java script files? Do I need to recompile the Microsoft ajax library source code or I just need to make the changes in: program files\Microsoft ASP.NET\ASP.NET 2.0 AJAX Extensions\v1.0.61025\MicrosoftAjaxLibrary\System.Web.Extensions\1.0.61025.0 ? This is the place I see javascript files.

    10x!

    Anton

    Comment by Anton Andreev — August 2, 2008 @ 2:48 pm

  2. Hi Anton,

    First, see my other post (the one right before this one chronologically) on this topic to see some rebuttal from Dave Reed of the ASP.NET AJAX team on this topic. He makes some good points and a couple suggestions on how to avoid this error.

    But, if you need to make the change, what you want to do is copy the the file at the location you mentioned, place it in an IIS accessible location, edit the copy, and then tell your ScriptManager to override the default MicrosoftAjax.js file. You can do that by adding a ScriptReference that overrides the MicrosoftAjax.js file. It will look something like this.

    The name “MicrosoftAjax” tells it to override the resourced MicrosoftAjax file.

    Good luck. Let me know if you need anymore help.

    Comment by Joel Rumerman — August 4, 2008 @ 6:33 am

  3. Hi, Joel

    Thank you for your answer.

    I have a problem with “Operation Aborted” and I also lose the css in IE.

    Before I read your post I have struggled with recompiling the Ajax library by reading this post to apply the modifications in this post.

    Indeed I succeeded in not only compiling it, but also making the runtime find the embedded javascript files. It was very hard as Javascript and .NET resources are the things I dislike most.

    You suggestion is probably better as one can replace the Ajax functionality only on this part of the site where the problem is experienced and recompiling the ajax library might cause some other future issues.

    You suggest that I put:

    One problem I see is that the javascript methods like “Sys$_Application$initialize” that your post changes are only available in the debug version of MicrosoftAjax – “MicrosoftAjax.debug.js”. The “MicrosoftAjax.js” is a file which is almost entirely on one line and I can not decode it.

    Any hints will be greatly appreciated as the project I am working on is quite important to me.

    Thanks,
    Anton

    Comment by Toncho — August 5, 2008 @ 3:40 am

  4. I see changes must be done in “MicrosoftAjax.debug.js” indeed and thats it.

    Comment by Toncho — August 5, 2008 @ 6:45 am

  5. Also in “Sys$_Application$_doInitialize()” there is a wrong operator “this._load_timer !== null” which should be “!=” I suppose.

    Comment by Toncho — August 5, 2008 @ 7:00 am

  6. Hi Anton,

    First, the !== is correct. It’s the strict non-equals comparison. It performs ever so slightly better than !=, but doesn’t compare two variables of different types correctly.

    Second, yes, fixing the problem in the release version of the Microsoft AJAX library is tough because its minified and there are no function names. But, it can be done by finding the right place to insert and modify the code. The functional code between the debug and release versions is the same (except for parameter validation) so it’s the same principle for both.

    Sounds like you figured it out.

    – Joel

    Comment by Joel Rumerman — August 5, 2008 @ 8:07 am

  7. Hi again,

    In Sys$_Application$initialize() there is something strange that gives error:

    document.write(”
    “);

    Is it correct? I receive some errors, but lets first check the code above.

    Maybe if you could post your “MicrosoftAjax.debug.js” as text file that you use in your code, we could save some questions and answers.

    Anton

    Comment by Anton Andreev — August 5, 2008 @ 9:22 am

  8. Anton,

    I’ll post the code as a text file on my releases page tonight sometime. (I’m at work and the code is at home.)

    I’ll update the post with a link.

    Comment by Joel Rumerman — August 5, 2008 @ 10:31 am

  9. Thanks.

    The truth is I am beginning to lose fate cause I have this error: “‘Sys_Application’ cannot be converted to type ‘Sys._Application'” which is triggered with the original “MicrosoftAjax.debug.js” when replaced with Scriptmanager.

    The version that comes when you install the source code of the Ajax library and the one disassembled give the same error without your changes being applied. I just do not know what to do.

    Anton

    Comment by Anton Andreev — August 5, 2008 @ 11:07 am

  10. What I noticed is that my project is set to .NET 3.5 and I probably use system.web.extensions assembly which is located in \program files\Reference Assemblies\Microsoft\Framework\v3.5\System.Web.Extensions.dll
    I have compared it to the \program files\Microsoft ASP.NET\ASP.NET 2.0 AJAX Extensions\v1.0.61025\MicrosoftAjaxLibrary\System.Web.Extensions\1.0.61025.0\MicrosoftAjax.debug.js and they are definitely different.

    How do you use the Ajax library in your project?

    -Anton

    Comment by Anton Andreev — August 5, 2008 @ 12:50 pm

  11. Hi Joel,
    you only found my IE specific function, my take to a cross-browser solution is located here:

    http://javascript.nwbox.com/ContentLoaded/

    The answer to “why linking other people’s code” is because they all did their part of the job, I only found the trick for IE which is only one component of the complete solution.

    Currently all browsers have a “DOMContentLoaded” event, except IE.

    The “IEContentLoaded” trick should fit perfectly with your needs, also I am not a .NET expert, but really this is the only thing that works on IE. In my current code there is a fallback for IFRAMES too.

    Hope it helps, cheers


    Diego Perini

    Comment by Diego Perini — September 11, 2008 @ 4:52 pm

  12. Hey Diego,

    Great! Thanks for providing the cross-browser link. I’ll definitely check it out and maybe update my fix with the new code.

    – Joel

    Comment by Joel Rumerman — September 11, 2008 @ 5:37 pm

  13. Hi Joel,

    Firstly, your site was of really great help to me.

    It seems that your implementation for IE is the same as with prototype. We were encountering some problems with this way specifically when we load the scripts dynamically. I have searched the internet regarding this prototype’s error and found this http://dev.rubyonrails.org/ticket/10942

    So for the update i think you should wrap the code like this

    if(document.readyState != “complete”) {
    document.write(”
    “);
    var deferScript = document.getElementById(“__ie_onload”);
    if (deferScript) {
    deferScript.onreadystatechange = function() {
    if (this.readyState == “complete”) {
    initializeDelegate();
    }
    };
    }
    }

    -adrien

    Comment by Adrien — September 12, 2008 @ 2:05 am

  14. the code just got messed up 😉
    btw just see the link for the details

    -adrien

    Comment by Adrien — September 12, 2008 @ 2:20 am

  15. Joel,
    just a further note…don’t leave out the “onreadystatechange” part of the IE specific code or the solution will fail in some edge cases and in iframes.

    By using “timeouts” alone it is not reliable enough and flickering may be annoying.

    This trick is currently used by most libraries out there, some of them failed the full implementation and they have had partial success.

    Google Ajax JS API is the only library having that trick fully implemented in current release. jQuery, ExtJS and Prototype will follow shortly, there are already patches written for all of them.

    I am interested in your results.


    Diego Perini

    Comment by Diego Perini — September 12, 2008 @ 1:44 pm

  16. […] have since commented that they’ve seen this problem in the wild.) I followed that post with another one where I provided code changes to fix the problem. Through comments on that post, talking with other […]

    Pingback by Fixing Sys.Application.initialize … Again « See Joel Program — October 3, 2008 @ 1:12 pm

  17. […] Update: Fixing Sys.Application.initialize […]

    Pingback by Sys.Application initialization steps « Update Panel .NET — October 17, 2008 @ 3:14 pm


RSS feed for comments on this post. TrackBack URI

Leave a reply to Diego Perini Cancel reply

Create a free website or blog at WordPress.com.