Sys.Application.add_load() vs. $(document).ready() vs. pageLoad()
I have page that has some javascript that needs to run at page load. Said javascript needs to locate the client-side component of a ServerControl, which it does with $find().
Of course, if I emit my code directly onto the page, it executes as the page is being read, and fails because nothing it depends on is yet initialized.
If I put my code inside a pageLoad() function, it runs just fine, because asp.net automatically wires up an onload handler for any function named pageLoad(). The problem is I really don't like the pageLoad() solution - mainly because it's a single global name. If I commit some code using pageLoad(), I just know that some other programmer is going to copy the approach, somewhere inappropriate, and we're going to end up with a page that includes two or more different pageLoad() functions, and the result will be a bunch of mysterious errors that will take forever to track down.
So, I put my code inside an anonymous function passed to jquery's $(document).ready(). This fails, because it runs before the ServerControl's client-side component exist开发者_Go百科s.
So, I put my code inside an anonymous function passed to by Sys.Application.add_load(). This also fails, because Sys is undefined.
So I finally settle on putting my code inside Sys.Application.add_load(), and then put that inside a function called by $(document).ready(). This works, but it gives nearly as much heartburn as pageLoad().
<script type="text/javascript">
$(document).ready(function(){
Sys.Application.add_load(function(){
var component = $find(<clientid>);
if (component) { <do something> }
});
});
</script>
There has to be a better way of handling this.
Any ideas?
If you have control over the code-behind, you can register the JavaScript to run at startup via something like:
this.Page.ClientScript.RegisterStartupScript(
this.GetType(),
"StartupScript",
"Sys.Application.add_load(function() { functioncall(); });",
true);
As long as your component has been loaded via Sys.Application.add_init() you should be fine...
I'd like to offer an alternative for those who are trying to build external libraries and trying to follow the unobtrusive javascript mindset as much as possible within .NET.
pageLoad is an excellent method to automatically wire up AJAX events which should fire on every page load (whether that load comes from a back button navigation to this page, a forward button navigation to the page, an AJAX callback, a standard page load, etc.), and since you don't have to worry about waiting for the ScriptManager to exist in the DOM, and it's named in a way that makes sense, for the most part (pageLoad vs. body.onload is slightly confusing for non .NET coders, but for a .NET person, it makes sense that pageLoad fires EVERY page load, including after callbacks, since the behavior is identical for the server side Page_Load). That being said, your concerns are good ones - you don't want two instances of pageLoad on a given page, as it will break. Here's a straight javascript solution which is safe for your developers to all follow, allows the use of pageLoad, doesn't fret about the DOM existing, ensures no code collisions, and works wonderfully for us.
In your main javascript library, define pageLoad, as well as a helper function:
if (!pageLoad) { var pageLoad = function (sender, args) {}; };
if (!mylibrarynamespace) {var mylibrarynamespace = {};};
if (!mylibrarynamespace.registerStartupScript) mylibrarynamespace.registerStartupScript = function (fn) {
/* create local pointer for added function */
var f = fn;
/* create pointer to existing pageLoad function */
var pLoad = pageLoad;
/* add new function pointer to pageLoad by creating new anonymous function (search Google for "javascript enclosures" for why pl and f will persist with this newly defined anonymous function) */
pageLoad = function (sender, args) {
pLoad(sender,args);
f(sender, args);
}
}
Then, to use it anywhere in your client side code, just do the following:
mylibrarynamespace.registerStartupScript(
function (sender, args){
/* do my AJAX always enabled startup code logic here*/
}
);
To make changing the functionality more easy, I recommend that the passed in anonymous function call a function within that page or control's subset of your library so that you only need to change your library's function declaration (in the file or at runtime dynamically) to change how the pageLoad for that particular part of your site works.
I believe if you add (or move) a ScriptManager control above your script block, you wouldn't need to wrap it in jQuery's $(document).ready()
function. By doing this apparently Sys
will be available as the ScriptManager:
injects the bulk of that JavaScript in the exact location that the ScriptManager control is positioned at in the page. [http://encosia.com/2007/08/16/updated-your-webconfig-but-sys-is-still-undefined/]
However, this solution may still give you heartburn as it would certainly not be obvious to any unsuspecting developer what the ScriptManager is actually providing here.
Another method where script references are added inside the ScriptManager (see "Better Way" discussed in the linked article above) doesn't sit that well with me either as I'm not a huge fan of the whole ScriptManager approach.
Try using the ScriptManager to register the client script block, but give a unique title for each additional function call especially if it has unique parameters if you are using the same function but with different parameter values. If you use the add_load technique it should only be used when the page load is not a post back i.e. only called once after a page refresh or page load without a post back.
try this instead: ScriptManager.RegisterClientScriptBlock(this, GetType(), "unique title", "alert('quick test');", true);
精彩评论