A nice facility of ASP.Net AJAX is that it lets you write a lot of functionality into the client whereas before you were stuck to simply writing it all on the server. One perfect example of this is authentication/authorization, which is now accessible from client script code through the AuthenticationService class.
I will lay out the basics of writing your own Authentication service (overriding the default one of ASP.net AJAX, which uses SQL Server, by the way). Many people will more than likely end up using their own implementation at some point anyway, not everyone has the benefit of creating a system from the ground-up.
First, enable the Authentication in your ASP.Net AJAX application by adding this to your web.config file:
<system.web.extensions> <scripting> <webServices> <authenticationService enabled="true" /> </webServices> </scripting> </system.web.extensions>
Second, enable FormsAuthentication in your web application (if it's not already), by adding this to your web.config file:
<system.web> <authentication mode="Forms"> <forms cookieless="UseCookies" loginUrl="~/login.aspx"/> </authentication> <system.web>
Third, you must specify a web service which the client code can asynchronously interact with to verify your login/logout actions. You do this by right-mouse clicking on the ScriptManager for your particular page and setting the AuthenticationService property to the URI of the service:

Fourth, you write your web service. Your web service must contractually have two methods with the following signatures:
[WebMethod] public bool Login(string userName, string password, bool createPersistentCookie) { //Place code here. return true; }
[WebMethod] public void Logout() { //Place code here. }
NOTE: You MUST decorate your WebService class with the [ScriptService] attribute (<ScriptService()> in VB.Net) or else you will get back a funky error from the script runtime. The error I got was "The server method 'Login' failed". I also got the error code 12031 when this occured.
Fifth, you add your script code to the aspx file to handle the authentication calls. The script code interactions with the AuthenticationService class in script and registers for callbacks that notify you about login/logout status.
<script language="javascript" type="text/javascript"> function Button1_onclick() { Sys.Services.AuthenticationService.set_defaultLoginCompletedCallback( OnLoginCompleted); Sys.Services.AuthenticationService.set_defaultFailedCallback( OnFailed); Sys.Services.AuthenticationService.set_defaultLogoutCompletedCallback( OnLogoutCompleted); Sys.Services.AuthenticationService.login("test", "test", false,null,null,null,null,"User Context"); return; } function OnLoginCompleted(validCredentials, userContext, methodName) { alert("You're logged in"); return; } function OnFailed(error, userContext, methodName) { alert("Failed to log in " + error.get_message() + ". " + error.get_statusCode()); return; } function OnLogoutCompleted(result) { alert("You've logged out"); return; } </script>
You then run the code and watch as the script will call your service method, and return back to the client framework whatever result it got from the service. You will need to flesh out the methods in your service to actually leverage the FormsAuthentication framework, etc. A typical situation would be like this:
[WebMethod] public bool Login(string userName, string password, bool createPersistentCookie) { if (Membership.Provider.ValidateUser(userName, password)) { FormsAuthentication.SetAuthCookie(userName, createPersistentCookie); return true; } return false; } [WebMethod] public void Logout() { FormsAuthentication.SignOut(); } |
RegisterDataItem is a subtle yet powerful member of the ScriptManager class. Unknown to many developers of ASP.Net AJAX is the fact that there can be a back-end data stream flowing from the server to the controls on the client whenever a partial rendering event takes place anywhere on the page. Through RegisterDataItem, you get to register a particular block of data (raw data or JSON serialized data) to a particular control on the client; and client code you place on the page can very easily update that control with its registered data. Example...
Say you have a calendar control on your web form inside an UpdatePanel, and outside the UpdatePanel there exists a Label control whose text should display the selected date of the calendar:

This design is for purposes of this example only; you probably wouldn't always set your system up this way, but for now it suffices.
The question is, how would you go about updating the Label as a result of a refresh of UpdatePanel1 in the example above? The answer is the RegisterDataItem.
In the postback method for clicking a particular date in the calendar, do this:
...when this method is called asynchronously, it will register the string data representing our date with Label_SelectedDate. We write a very simple block of client code immediately after our ScriptManager tag in the page: <script> Sys.WebForms.PageRequestManager.getInstance().add_pageLoading(PageLoadingHandler);
function PageLoadingHandler(sender,args){ var dataItems = args.get_dataItems(); if($get('Label_SelectedDate')!==null) $get('Label_Selecteddate').innerHTML = dataItems['Label_SelectedDate']; return; } </script>
The result will be, whenever you select a date within the calendar, the AJAX runtime will properly update the contents of the label:

This works because the ASP.net AJAX runtime sends the serialized data down to the client framework during the partial rendering of the calendar; the client framework will snag the appropriate bits from this feed and update the controls accordingly through the script we wrote. The "trick" is registering a method with the pageLoading event of the PageRequestManager.
One caveat to using the ScriptManager on your pages is that you can only have one ScriptManager reference at a time. What if you are writing a content page for a master page that has a ScriptManager on it; but you need to now make sure that the ScriptManager references a particular service your content page needs - what do you do? You could check out the MaterPage and make the necessary modifications - but why? That's actually not a very good idea because it kind of breaks the whole concept of master/content page seperation; especially in scenarios where a certain developer can't or shouldn't have access to the master page (say you're farming a particular content page out to a consultant, etc).
The answer when you need to reference a service from your content page and yet the ScriptManager resides on the MasterPage is a ScriptManagerProxy. The ScriptManagerProxy works by detecting the main (real) ScriptManager on your page at runtime and hooking itself to that ScriptManager, making sure that any references given to it are also given to the real ScriptManager. In fact, it's constructor takes a real ScriptManager as a parameter:
...and it's OnInit method (called by the ASP.net page framework when the page is being built) is where the proxy registers itself with the page's real ScriptManager:
As a result of this registration, when the real ScriptManager builds all references for Services during OnPagePrePrenderComplete, its RegisterServices() method will collect all service references from registered ScriptManagerProxies....
if (this._proxies != null) { foreach (ScriptManagerProxy proxy in this._proxies) { proxy.RegisterServices(this); } }
ASP.Net AJAX introduces the ScriptManager, and the ScriptManager introduces a set of API that look just like a set exposed by the ClientScriptManager in "traditional" ASP.Net; things like RegisterStartupScript, RegisterClientScriptInclude, etc.
The question is: why?
The answer is actually pretty elementary, but in really understanding it you kind of come to another understanding of ASP.net AJAX; well, actually, AJAX in general.
The way the ClientScriptManager (Me.Page.ClientScript or this.Page.ClientScript) works in "traditional" ASP.Net it maintains a list of all scripts on the Page, and when asked to render by the Page object, it properly works those scripts into the page output. The thing is, in ASP.Net AJAX, the page lifecycle is hijacked by the ScriptManager during partial rendering and therefore the Page is never asked to render; the ScriptManager takes care of choosing what controls to render, etc.
So code like this:
protected void Button1_Click (object sender, EventArgs e){ this.LinkButton1.Visible=true; Page.ClientScript.RegisterStartupScript(this.GetType(), "linkonclick", "<script>document.getElementById('"+this.LinkButton1.ClientID+"')"+".onclick = function(){('click',alert('hello world'),true);}</script>"); } }
.....will NOT work if Button1 is inside of an UpdatePanel - the ClientScriptManager is never asked to render its scripts; you're basically adding scripts to it for no good reason. This code DOES work, however, if Button1 is not within an UpdatePanel.
To make the above code work in an UpdatePanel you do the following....
protected void Button1_Click (object sender, EventArgs e){ this.LinkButton1.Visible=true; ScriptManager.RegisterStartupScript(this, this.GetType(), "linkonclick", "<script>document.getElementById('"+this.LinkButton1.ClientID+"')"+".onclick = function(){('click',alert('hello world'),true);}</script>", false); } Nothing really changed except that we're now calling through the ScriptManager's static version of the RegisterStartupScript method. Since the script is registered with the ScriptManager, it is sent down to the client in the partial page refresh response as something the client script needs to introduce into the page DOM. Everything works now.
This has been done before here, but I would like to do this in a different way: by actually showing you how to write a web control that utilizes ASP.Net AJAX in a step-by-step manner. Let's start.
First thing is a bit of cursory-level knowledge of what it means to be an IScriptControl. As it were, the ASP.Net AJAX is - just like everything else in the .Net runtime - strictly object-oriented. As a result of this object-oriented'ness, everything is done as a class - and a control that can utilize the ASP.Net AJAX runtime, therefore, is best done as its own, independent type. Therefore, an IScriptControl is the interface that a control type must implement if it is to take part in the rich AJAX experience (or just utilize the client script libraries supplied by ASP.Net AJAX). My goal is to write some stupid, useless control; but while doing so teach you (and probably me) something - so I'm writing a button that cannot be clicked . I told you it was useless.
First, the shell:
namespace UnclickableButton { public class TheUnclickableButton : Button, IScriptControl { public IEnumerable<ScriptDescriptor> GetScriptDescriptors() { return; }
public IEnumerable<ScriptReference> GetScriptReferences() { return; }
public String InactiveCSSClass { get { return null; } set { } } public String ActiveCSSClass { get { return null; } set { } } } }
Here you see the very elementary basics behind my control. I simply implement the button by inheriting from it; and also implement the whole IScriptControl interface. First you notice the IEnumerable methods, these are implemented to implement the IScriptControl interface. Second, you'll notice the properties, these are accessor methods that will be used to grab hold of the CSS style sheets needed to implement my unclickable button. Let's proceed...
...we have the server-side code done. But no AJAX application would be complete without associated client-side code. When building web controls for use in ASP.Net AJAX, careful attention was made to the fact that whatever settings, options, properties, etc. you set from the server should be reflected on the client; and so you must implement a client-side object which corresponds to your server-side object and the two will be knitted together thanks to the ASP.net AJAX runtime.
My server-side object has two accessor methods InactiveCSSClass and ActiveCSSClass, so my script-version of thisobject should too. Even though my script object is written in ECMA script (JavaScript), thanks to the ASP.Net AJAX runtime it can be object-oriented, and so requires a namespace to be programmable. The following script can then be written within a new JS file: Type.registerNamespace('BensSamples');
BensSamples.UnclickableButton = function(element){ BensSamples.UnclickableButton.initializeBase(this,[element]);
this._ActiveCSSClass = null; this._InactiveCSSClass = null; }
Here you see I registered a rather self-serving namespace and created what can be likened to as my client object's constructor. JavaScript is a prototype-based language, and so the actual type needs to be designed as a prototype. To fill in the datatype, you must fill in the prototype for your object as such:
BensSamples.UnclickableButton.prototype = {
initialize : function() { // initialize the base BensSamples.UnclickableButton.callBaseMethod(this,'initialize');
// register delegates this._onActiveHandler = Function.createDelegate(this,this._onBlur); this._onInactiveHandler = Function.createDelegate(this,this._onFocus);
// register handlers $addHandlers(this.get_element(), { 'focus' : this._onFocus, 'blur' : this._onBlue}, this);
// set default css style this.get_element().className = this._ActiveCSSClass; }
dispose : function() { // release the handlers $clearHandlers(this.get_element());
// call to the base to do its dispose BensSamples.UnclickableButton.callBaseMethod(this,'dipose'); }
// flesh out the actual handlers _onFocus : function(e) { if(this.get_element() && !this.get_element().disabled){ // if we have something real and it's enabled, set it to the // disabled class type. this.get_element().className = this._InactiveCSSClass; return; } } _onBlur : function(e) { if(this.get_element() && !this.get_element().disabled){ // if we have something real and it's enabled, set it to the // enabled class type. this.get_element().className = this._ActiveCSSClass; return; } }
// create accessor methods corresponding to the server control get_ActiveCSSClass : function(){ return this._ActiveCSSClass; } set_ActiveCSSClass : function(value){ if(this._ActiveCSSClass!=value){ this._ActiveCSSClass = value; // let everyone know the property changed this.raisePropertyChanged('ActiveCSSClass'); } }
get_InactiveCSSClass : function(){ return this._InactiveCSSClass; } set_InactiveCSSClass : function(value){ if(this._InactiveCSSClass!=value){ this._InactiveCSSClass = value; // let everyone know the property changed this.raisePropertyChanged('InactiveCSSClass'); } } }
// register the class BensSamples.UnclickableButton.registerClass('BensSamples.UnclickableButton',Sys.UI.Control);
if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();
We now have a ECMA script class that lays out all of the definitions for the client type corresponding to the server type. We now need to hook the two together. To do this, you utilize the methods of the IScriptControl interface. The two methods have the following purpose:
- GetScriptDescriptors(): returns a ScriptDescriptor object that identifies the metadata or properties/events/etc. of the client type that will be instantiated and used on the client. This is what effectively tells the ASP.Net AJAX runtime about your class and what it will do.
- GetScriptReferences(): this returns a ScriptReference object that contains a reference to an external script file that fleshes out the functionality of your object.
The two methods will return all that is required for the AJAX runtime to fully instantiate, utilize and give you power over your ASP.Net AJAX control, on the client. Let's go back to our C# code and fill in the methods:
namespace UnclickableButton { public class TheUnclickableButton : Button, IScriptControl { public IEnumerable<ScriptDescriptor> GetScriptDescriptors() { ScriptControlDescriptor descriptor = new ScriptControlDescriptor( "BensSamples.UnclickableButton", this.ClientID); descriptor.AddProperty("InactiveCSSClass", this.InactiveCSSClass); descriptor.AddProperty("ActiveCSSClass", this.ActiveCSSClass);
return new ScriptDescriptor[] { descriptor }; }
public IEnumerable<ScriptReference> GetScriptReferences() { ScriptReference reference = new ScriptReference(); reference.Path = "UnclickableButton.js";
return new ScriptReference[] { reference }; }
private String _InactiveCSSClassPath = String.Empty; public String InactiveCSSClass { get { return _InactiveCSSClassPath; } set { _InactiveCSSClassPath = value; } } private String _ActiveCSSClassPath = String.Empty; public String ActiveCSSClass { get { return _ActiveCSSClassPath; } set { _ActiveCSSClassPath = value; } } } }
Now, let's create a CSS file and add it to our project and the webform atop which our UnclickableButton will be placed:
(StyleSheet.css Contents:)
body {} .InactiveButton{visibility:hidden;} .ActiveButton{visibility:visible; }
Let's reference our new control from the .ASPX page:
<% @ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %> <%@ Register Namespace="UnclickableButton" TagPrefix="BensSample" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <link href="StyleSheet.css" rel="stylesheet" type="text/css" /> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>Untitled Page</title> </head> <body> <form id="form1" runat="server"> <asp:ScriptManager ID="ScriptManager1" runat="server" /> <br /> <BensSample:TheUnclickableButton Text="Click Me!" ID="Button1" runat="server" InActiveCSSClass="InactiveButton" ActiveCSSClass="ActiveButton" /> </form> </body> </html>
Now, what happens when we run this? Unfortunately, nothing...why? The ScriptManager is the arbiter of all things ASP.Net AJAX; and for any control to participate within the ASP.net AJAX runtime, it must first be registered with the ScriptManager. So, we add the following.....
protected override void OnPreRender(EventArgs e) { if (!this.DesignMode) { _scriptManager = ScriptManager.GetCurrent(this.Page); if (_scriptManager != null) { _scriptManager.RegisterScriptControl(this); } else throw new ApplicationException("You must have a ScriptManager!"); }
base.OnPreRender(e); }
protected override void Render(HtmlTextWriter writer) { if (!this.DesignMode) _scriptManager.RegisterScriptDescriptors(this);
base.Render(writer); }
We should now be able to run our software....the complete source code follows:
Server:
using System; using System.Collections.Generic; using System.Text; using System.Web.UI; using System.Web; using System.Web.UI.WebControls;
namespace UnclickableButton { public class TheUnclickableButton : Button, IScriptControl { public TheUnclickableButton() { return; }
private ScriptManager _scriptManager = null; protected override void OnPreRender(EventArgs e) { if (!this.DesignMode) { _scriptManager = ScriptManager.GetCurrent(this.Page); if (_scriptManager != null) { _scriptManager.RegisterScriptControl(this); } else throw new ApplicationException("You must have a ScriptManager!"); }
base.OnPreRender(e); }
protected override void Render(HtmlTextWriter writer) { if (!this.DesignMode) _scriptManager.RegisterScriptDescriptors(this);
base.Render(writer); }
public IEnumerable<ScriptDescriptor> GetScriptDescriptors() { ScriptControlDescriptor descriptor = new ScriptControlDescriptor( "BensSamples.UnclickableButton", this.ClientID); descriptor.AddProperty("InactiveCSSClass", this.InactiveCSSClass); descriptor.AddProperty("ActiveCSSClass", this.ActiveCSSClass);
return new ScriptDescriptor[] { descriptor }; }
public IEnumerable<ScriptReference> GetScriptReferences() { ScriptReference reference = new ScriptReference(); reference.Path = "/AJAXTest/UnclickableButton.js";
return new ScriptReference[] { reference }; }
private String _InactiveCSSClassPath = String.Empty; public String InactiveCSSClass { get { return _InactiveCSSClassPath; } set { _InactiveCSSClassPath = value; } } private String _ActiveCSSClassPath = String.Empty; public String ActiveCSSClass { get { return _ActiveCSSClassPath; } set { _ActiveCSSClassPath = value; } } } }
Client
Type.registerNamespace('BensSamples');
BensSamples.UnclickableButton = function(element){ BensSamples.UnclickableButton.initializeBase(this,[element]); this._ActiveCSSClass = null; this._InactiveCSSClass = null; }
BensSamples.UnclickableButton.prototype = {
initialize : function() { // initialize the base BensSamples.UnclickableButton.callBaseMethod(this,'initialize');
// register delegates this._onActiveHandler = Function.createDelegate(this,this._onBlur); this._onInactiveHandler = Function.createDelegate(this,this._onFocus);
// register handlers $addHandlers(this.get_element(), { 'focus' : this._onFocus, 'blur' : this._onBlur}, this);
// set default css style this.get_element().className = this._ActiveCSSClass; },
dispose : function() { // release the handlers $clearHandlers(this.get_element());
// call to the base to do its dispose BensSamples.UnclickableButton.callBaseMethod(this,'dispose'); },
// flesh out the actual handlers _onFocus : function(e) { if(this.get_element() && !this.get_element().disabled){ // if we have something real and it's enabled, set it to the // disabled class type. this.get_element().className = this._InactiveCSSClass; return; } }, _onBlur : function(e) { if(this.get_element() && !this.get_element().disabled){ // if we have something real and it's enabled, set it to the // enabled class type. this.get_element().className = this._ActiveCSSClass; return; } },
// create accessor methods corresponding to the server control get_ActiveCSSClass : function(){ return this._ActiveCSSClass; }, set_ActiveCSSClass : function(value){ if(this._ActiveCSSClass!=value){ this._ActiveCSSClass = value; // let everyone know the property changed this.raisePropertyChanged('ActiveCSSClass'); } },
get_InactiveCSSClass : function(){ return this._InactiveCSSClass; }, set_InactiveCSSClass : function(value){ if(this._InactiveCSSClass!=value){ this._InactiveCSSClass = value; // let everyone know the property changed this.raisePropertyChanged('InactiveCSSClass'); } } }
// register the class BensSamples.UnclickableButton.registerClass('BensSamples.UnclickableButton',Sys.UI.Control);
if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();
I'd like to make a visual version someday. Please let me know if you see any errors.
1. .ctor() 2. ScriptManager.OnInit() a. Register for Page.InitComplete, Page.PreRenderComplete, Page.PreRender b. PageRequestManager.OnInit() 3. ScriptManager.OnPageInitComplete() a. sets initCompleted boolean to true. 4. ScriptManager.OnPagePreRenderComplete() a. If postback is Async 1. If page supports partial rendering a. IPage.ClientScript.GetPostBackEventReference 2. ScriptManager.RegisterGlobalizationScriptBlock a. ClientCultureInfo.GetClientCultureScriptBlock(CurrentCulture) b. If culture script found, ScriptRegistrationManager.RegisterClientScriptBlock 1. Adds reference to ScriptRegistrationManager.ScriptBlocks 3. ScriptManager.RegisterScripts a. ScriptManager.CollectScripts() b. ScriptManager.AddFrameworkScript("MicrosoftAjax.js") c. If page supports partial rendering, ScriptManager.AddFrameworkScript("MicrosoftAjaxWebForms.js") d. Foreach script retrieved so far, ScriptManager.OnResolveScriptReference e. ScriptManager.RemoveDuplicates : removes duplicate script references so far f. Foreach script retrieved after duplicate checking, if should load script before UI then ScriptManager.RegisterClientScriptIncludeInternal 1. ScriptRegistrationManager.RegisterClientScriptInclude a. Adds reference to ScriptRegistrationManager.ScriptBlocks else ScriptManager.RegisterStartupScriptInternal 2. ScriptRegistrationManager.RegisterStartupScript a. Adds reference to SCriptRegistrationManager.ScriptStartupBlocks 4. ScriptManager.RegisterServices a. Foreach ServiceReference in _services, ServiceReference.Register() b. Foreach ScriptManagerProxy in _proxies, ScriptManagerProxy.RegisterServices c. If EnablePageMethods, PageClientProxyGenerator.GetClientProxyScript. If script then RegisterClientScriptBlockInternal. 1. Adds to ScriptRegistrationManager.ScriptBlocks b. Else 1. ScriptManager.RegisterScripts a. See above. 5. ScriptManager.OnPreRender() a. If postback is Async 1. PageRequestManager.OnPreRender a. IPage.SetRenderMethodDelegate, sets to PageRequestManager.RenderPageCallback 6. PageRequestManager.RenderPageCallback a. PageRequestManager.ProcessUpdatePanels b. IHtmlForm.SetRenderMethodDelegate(PageRequestManager.RenderFormCallback) c. IHtmlForm.RenderControl d. Encode hiddenField to output HTML stream e. Encode asyncPostBackControlIDs to output HTML stream f. Encode postBackControlIDs to output HTML stream g. Encode updatePanelIDs to output HTML stream h. Encode childUpdatePanelIDs to output HTML stream i. Encode panelsToRefreshIDs to output HTML stream j. Encode asyncPostBackTimeout to output HTML stream k. If formAction is something, encode formAction to output HTML stream l. If pageTitle is something, encode pageTitle to output HTML stream m. PageRequestManager.RenderDataItems 1. Foreach ScritpDataItem in PageRequestManager._scriptDataItems call EncodeString to "dataItemJson" : "dataItem" n. PageRequestManager.ProcessScriptRegistration 1. RenderActiveArrayDeclarations(this._updatePanelsToRefresh, writer); 2. RenderActiveScripts(this._updatePanelsToRefresh, writer); 3. RenderActiveSubmitStatements(this._updatePanelsToRefresh, writer); 4. RenderActiveExpandos(this._updatePanelsToRefresh, writer); 5. RenderActiveHiddenFields(this._updatePanelsToRefresh, writer); 6. RenderActiveScriptDisposes(this._updatePanelsToRefresh, writer); o. PageRequestManager.ProcesFocus 7. ScriptManager.Render() a. PageRequestManager.Render() 1. If Not postback is Async and page supports partial rendering a. IPage.VerifyRenderingInServerForm() b. PageRequestManager.RenderPageRequestManagerScript b. If Not postback is Async 1. IPage.ClientScriptManager.RegisterStartupScript("AppInitialize")
The task is to add a confirmation dialog whenever someone clicks a Delete or Add or whatever button/link/image on your GridView or Repeater. To accomplish this will require a bit of JavaScript, but it's really not hard at all.
The Repeater has the ItemDataBound and the GridView has the RowDataBound events, and in order to attach your JavaScript to an object within EACH row or item, you will need to hook this event (my samples will be with the GridView, but the Repeater uses the same methodology).
protected void GridView_Views_RowDataBound(object sender, GridViewRowEventArgs e) { if (e.Row.RowType == DataControlRowType.DataRow) { ImageButton button = (ImageButton)e.Row.Cells[0].Controls[0]; button.Attributes.Add( "onclick", "if(!confirm('Delete this item?'))return false;");
return; } return; }
Many people become confused on how to get at a particular control once rendered into the browser DOM with their script because the name changes for each row or item of the parent control (GridView or Repeater). They become curious how to attach their script to something seemingly random such as:
ctl00$ContentPlaceHolder1$Repeater1$ctl00$btnDelete
The answer is, you don't. You attach it to the object as it's being rendered into the newly bound row like above. Here, nothing has been scoped and named out for the client DOM, it's still just an object in memory.
The way the script works is simple: the event model of the browser will prevent something from bubbling if you return a false or 0 value from the on* handler (such as onclick above). If you return a success or 1 value, then the event will continue to trickle up the call chain to whomever else is registered to handle it (which, in this case, would be the ASP.Net client page framework which initiates the __doPostback() call). Recommended reading:

ASP.Net AJAX offers a few helper routines for writing debug/trace statements in your clientside scripts. One of these helper routines is Sys.Debug.trace(), and it is really only a thin wrapper over a block of code that identifies the host browser and then utilizes its built-in tracing facilities.
Within MicrosoftAjax.js you'll find that trace() internally calls appendConsole(). The internals of appendConsole() looks like this:
if(typeof Debug!=="undefined"&&Debug.writeln) Debug.writeln(a); if(window.console&&window.console.log) window.console.log(a); if(window.opera) window.opera.postError(a); if(window.debugService) window.debugService.trace(a);
The first conditional is detecting if you're running Internet Explorer by seeing if Debug and Debug.writeln have definition. An example of what Debug.writeln() does can be found here.
It appears as though window.console.log() is for Safari, window.opera.postError() is for Opera (obviously), and window.debugService.trace() is for WebDevHelper (I believe). I don't see one for FireFox, does FireBug not have tracing support? Certainly it must.....
|