locked
FINALLY a comprehensive example of Queued Script Issue! RRS feed

  • Question

  • User-1920956665 posted

    As promised, I FINALLY have put together a comprehensive example that shows my problems with queued script timings.  You will find that sometimes this exampe will work and sometimes it will not work (and you will get errors related to the "Model" class not existing).

    You can download the zipped project here. It contains a full example of the Model-View-Controller type pattern I was using (inside the Queued.Framework assembly) as well as some data binding hookups on a derived AjaxTextBox control contained therin. You will see scripts related to the actual page to be loaded, scripts inside of the assembly, etc.

    There is inheritance in the Javascript as well, as reflected by the base model, controller, and input control objects in the Queued.Framework.Common folder, where the base script reside.

    The only thing that does not work on the sample page is the "Add" functionality.  I did not take the time to fully implement that, because it really has nothing to do with the error.  However, it shows an example of what I was trying to accomplish.  Imagine 80 of these controls on a page at once (inside of tabs, etc. so they would not necessarily all be visible at once).

    When you click "select" from the results table of the initial web service call, you will see the contents of the text box controls change, which is the purpose of the homegrown data binding "replacement" technique, so I can not rewrite wire-up code for 80 instances of these controls.  This architecturally explains the need for the controller and model constructs.

    Feel free to pick at the architecture.  Tell me if there is something I should have done different or if I am on the wrong track as far as how I wired all this up.

    Thanks for your time.  This will take a bit of time to look at.  It is a lot of code for a simple example, but the applications I am really writing need this type of framework (or something like it). Otherwise, I would never finish any non-trivial project.  I would spend months just handling the placement and wire-up of text boxes, etc. for objects with lots of properties.

     The final goal is to generate pages that do editing and display from metadata in my database, so I never have to write much of this code at all.  I had CodeSmith based templates already for Beta 1 of ASP.NET Ajax, and that worked great!  I looked like a genius that could whip out an entire AJAX application in minutes.  I want that feeling back and cannot get it until I have a reliably functioning pattern that I can then "template" and "generate."

    If this ever works in that manner, I will wrap my base code for doing this up as a library and put it on CodePlex for all to use.  When this worked, it cut down the time for building properly tiered user interfaces from weeks to days.  It was cool until I tried to switch to Beta 2.

     

    Saturday, December 2, 2006 10:53 AM

All replies

  • User-1920956665 posted

    Updated!

    I have updated my example to include full functionality with validation and the "Add" function for the example working (it adds to an in memory cache of "Person" objects and updates the list display).

    The example can be downloaded from the same location. I still have the same issues with intermittent errors on definitions being available--especially during initial debugging and over a WAN connection. I see that no one has responded to the original posting, but I am not sure if that was because the example did not provide absolutely full functionality as would be intended. This has full functionality according the architecture and model I was trying to implement.

    Sunday, December 3, 2006 8:05 AM
  • User287763314 posted
    hello.

    first, thanks to Garbin for helping me with this one.

    second, the problem you'r having is NOT a bug with the scriptloader. it's a side effect of the way jscript works.

    so, what's the problem: you get an exception saying that the derived type is not derived from a base type (ie, if A inherits from B, you get an error saying that B is not a base for A).

    why does this happen? easy one: you're loading the files that have the code for the base class 2 times.

    how and where? you're loading the common.js file in 2  places:

    1. it the scripts property section
    2. you're also injecting it since your textbox control adds a reference to it

    most of the time, there's nothing wrong with this (besides downloading 2 times the same file, that is). in this case, the problem is that you're also loading a third file between each load and this is where you're getting your bug. let me explain this a little better with a simple example. suppose you have a class called A, in namespace Test (file A.js):

    Test.A = function(){
    }
    Test.A.registerClass("Test.A");

    what does this do? well, it performs an internal registration and creates a global object to which you can refer by using the name Test.A.

    now, let's suppose that you have a derived class called B (in file B.js):
    Test.B = function(){
    }
    Test.B.registerClass("Test.B", Test.A);

    what does this do? well, simple, right. hope you're not bored yet :) besides the obvious, you should also know that Test.B will keel an internal field which points to the base class type (the field is called __baseType, if i'm not mistaken!).

    now, what happens when you load the following sequence of files:

    A->B->A

    don't know yet? ok, let me show.

    reloading A will redefine Test.A with a new object which has the same structure as the first one. what you must keep in mind is that we're talking about different instances of the object. 

    this is where your code will break because after reloading A.js, Test.B.__baseType will no longer be equal to Test.A. sad, but true!

    so, going back to your case, you can solve this by removing the entry for common.js from the scripts property and by adding it to the end of the page by using the script element.

    btw, you have to do this because currently the script files that you define in the scripts property are inserted in the page before the files you register in your control. though i haven't tried it, you could also try to register the js file in the prerender event of the page (maybe it'll be after registered control files).

    so, in conclusion, this is not a bug in the way the ScriptLoader works: it's doing its job and it's loading the files in the order you're passing them. what you could say is that maybe the server registered files should be added to the page before the files specified on the scripts element (though i would still need to sudy all cases to be sure about it).

    hope this explains the problem.
    Sunday, December 3, 2006 6:12 PM
  • User-1920956665 posted

    I have not analyzed your entire response yet, but I wanted to ask a quick question:

     Doesn't the ScriptManager remove ALL duplicate script references regardless of where they come from before render the script tags to the page in or after the OnPreRender() override?  I looked this up in the reflected source for the ScriptManager as I was doing this, since it is impossible to know, from page to page, how you might need references to appear--especially since user controls with ScriptManager proxies, etc. can be involved.

    According to what I seemed to see, registering a script reference twice--even through two different mechanisms (IScriptControl AND ScriptManager references and ScriptManager proxies) still should yield a single script tag source.

    Is this not true?  I will reread the rest for any potential errors on my part.

    Sunday, December 3, 2006 6:24 PM
  • User287763314 posted
    hello.

    yes and no. you'll get that behavior if you register scripts with the scriptmanager registerXXX methods.

    though i don't have the time to check it now, i'm almost sure that control registration isn't performed on the same list maintained by the scriptmanager control. in fact, i do think that scripts references are added during the prerender and that controls only insert their scripts during the render phase (or is it the other way around?)...if you check this, please let us know.


    Sunday, December 3, 2006 6:45 PM
  • User-1920956665 posted

    I did check this fact, and it is during the OnPreRender override that all this takes place.  In fact, if you try to do any of this any earlier (like in OnInit()), you get an exception thrown.

    ScriptManager.RegisterScripts() is called during the OnPreRender() override for the ScriptManager, and it seems to do the following:

    1. Collects all scripts registered (root ScriptManager and proxies).
    2. Adds framework scripts to the top of the list (MicrosoftAjax.js) along with all script references from #1 via ScriptManager.AddFrameworkScript().
    3. If partial rendering is supported, MicrosoftAjaxWebForms.js
    4. Calls resolve script reference event callbacks in loop.
    5. Duplicates that were collected are removed.
    6. For all remaining scripts in the list, Page.ClientScript.RegisterStartupScript() is called. This is where a flag (if only accessible) would have loaded the script reference Page.ClientScript.RegisterScriptInclude() instead. I would rather have this, but that flag is inaccessible. Instead, the queued startup script is assembled and registered.
    7. Application services are configured.
    The Beta 1 version simply called Page.ClientScript.RegisterScriptInclude(), and although I understand what you are saying, everything worked fine with no problems when doing that.  In Beta 2, I have all kinds of problems--especially in situations where there are combinations of user controls (examples not provided in my sample), etc.  According to the logic, no matter how I register the script (unless I register it manually myself outside the context of the ScriptManager, which seems dicey to try since the script ordering is controlled by the ScriptManager to ensure that the Framework Scripts come first), duplicates should never matter unless I actually have the same code in two separate files that cannot be recognized as duplicates.
    Sunday, December 3, 2006 7:46 PM
  • User287763314 posted

    hello.

     to be honest, i don't recall how it worked in beta 1 (ie, i don't recall the internals of script registration).i agree about adding the script at the end of the page...

    it's also fair to say that the problem is not related with the way scriptloader works: it's really a side effect of how script registration is performed in the server side (where the server control is performing its registration to a different list).

     
    btw, according to bertrand, in rc1 trying to register a class will give you an exception and you'll be able to understand the error in an easy way. well, i do believe that now we should ask the team to unify the control script injection mechanism so that it's shared with the script references loaded by the scriptmanager control 8just my opinion though)
     

    Monday, December 4, 2006 4:35 AM
  • User-1155260916 posted

    Duplicate entries of script libraries 'registered' with the ScriptManager should be resolved with all models of library registration, (first one wins, unless there is a path-based reference), and it is entirely feasible that there is an issue there :). You have a client type, AjaxTextBox and that it is queued with the ScriptLoader using the webresource.axd handler. Looking at the script it does not have the Sys.notifyScriptLoaded() call at the bottom.. (Let's get that resolved, then we can see if the dupe entry is being made, I did not see this in my cursorary glance).. It looks like in your IScriptControl, you attempt to register the script as:

    yield return new ScriptReference(Page.ClientScript.GetWebResourceUrl(typeof(Common.CommonScripts), "Queued.Framework.Common.Common.js"));
    yield return new ScriptReference(Page.ClientScript.GetWebResourceUrl(typeof(AjaxTextBox), "Queued.Framework.Web.AjaxTextBox.js"));

    You are making calls through to the old model of script registration here, using Page.ClientScript. Rather than doing this, simply set the assembly and name properties for a new ScriptReference, say:

    ScriptReference reference = new ScriptReference();
    reference.Assembly =
    this.GetType().Assembly.FullName;
    reference.Name = _AjaxTextBox_embeddedLibraryName;

    Here's some further background.

    There are effectively 3 ways of registering the scripts with new 'Atlas' type controls and web apps. (1) Through the scripts collection (the primary purpose of this being for the declarative model of defining scripts, although you can add to the colelction), (2) through the IScriptControl.GetScriptReferences API; the call to the control happens in PreRender and (3) using the ScriptManager.RegisterXXXX APIs which you can think of as being more for older controls that want to get a form of compat with async-post (UpdatePanel) scenarios and their script libraries do not necessarily have an Atlas dependency.

    Note that the scriptresource handler is used in all cases that assembly-based script libraries are used with the ScriptManager instance. In the case that no ScriptManager instance is used, or the Page.ClientScript.XXXX style APIs are used, then the webresoruce handler is used. The former gives you the ability to get compression and localization models built-in, the latter means no client-side localization and compression through the module (not built-in).

    Hope this helps ..

     

    Monday, December 4, 2006 12:54 PM
  • User-1920956665 posted

    Thanks very much for your response!  It does help, and, by the way, the lack of the Sys.Application.notifyScriptLoaded() call missing from my drop of sample code was an oversight that I had already fixed but had the same results.

    I used some the suggestions from Luis Abreu with some success, but the solution was not as well suited for complex scenarios involving master pages, user controls, and other page artifacts that could be owned by a variety of different other developers (and asking them to put special little hacks on their pages to suit a particular situation just would not really work).  Master pages are the most interesting of the scenarios, since the "fix" involved adding a manual script reference at the very end of the page for my simple example.

    I have always found ways of working around the problem to get a specific thing working, but I have had trouble outlining an overriding strategy for how work should be done in all instances (to this point, it has been a hack here, a hack there depending on whether I was using the AtlasTookit or not--among other things).

    I am not even sure if the architecture for MVC I have laid out to make data binding (without XML-Script and Preview controls), validation, and data view synchronization work is actually the right idea for the way Atlas is architected.  It would be great to get some guidance on that for data driven applications with lots of objects, lots of attributes, and lots of connected views and lists of all of them.  This has posed different challenges from the typical samples I have seen to this point.

    Thanks again!

    Monday, December 4, 2006 1:28 PM