My best friend Fregas once said ASP.NET is hard. No, actually I said that. He said said ASP.NET is Complicated. And of course, I agree with him. I don't agree with him because he's got a funny little Unibrow or because he struts around with mannerisms resembling an African primate. Its because he's "usually" right.

I found this silly little issue with DefaultButton today that makes me really truly believe that ASP.NET can and sometimes is, complicated. 

Now, as a disclaimer let me start out by saying, I have no idea what I'm doing.. That being said, I recently jumped on the tail end of a project that's going live this week. The App is done and we've just been minor bugs. So today after I resolved some bugs I decided to fix an irritating little UI bug that somehow got over looked. Its the omnipresent "Why Can't I submit the damn form with my freaken enter key. And why does that just take me to the home page ? " issue. Well, its because your logo is in the top left corner of your site and that gets focus.  Yea. I know you know. Well, in 1.x it was possible to get around the issue with some Mister JavaScript code or display:none'ing buttons but those days are long gone, right? These are the days of LINQ and CLRs running on the client. Right? Well, I got hung up today using that simple little property. Let me give you a play by play:

"Ahh, here's the form. Ok. Here's the property. Let me make that equal to my loginButton. Geesh.. That was easy. Lets let this puppy breathe and try my totally awesome, radically forward thinking, many focus group led, enter-key-finally-submits-the-form functionality. What's this? NOOOO!"

Cut... Error. DefaultButton can only be used on IButtonControls? WTF! I'm using a button, well ok an ImageButton but its still a button right?.. It didnt say "DefaultButton can only be used on IButtonButNotImageButtonControls. Wait. Let me see if that's true. I Bust open my Object browser. Wait a sec. ImageButton implements IButtonControl. Curse you ASP.NET! I would have gotten way with my plans of totally amazing, ground breaking, web site history making functionality of making the enter key submit the stupid form with one Property setter.

Well, I'm really still not sure why but for some reason, I couldn't get my ImageButton to be the DefaultButton. If anyone knows please let me know (as if anyone really reads this blog)

Well, I've been writing ASP.NET for some time now so it was time to get creative.

It turns out that <asp:Panel /> has a defaultButton property. What I actually did was a mix of 1.x/2.0 by display:none'ing an <asp:Button id="MyButton"  /> inside a <asp:Panel defaultButton="MyButton" />

Heres what it looks like:

   1: <asp:Panel runat="server" id="LoginPanel" DefaultButton="btnLogin">
   2:     <asp:Button runat="server" id="btnLogin" CssClass="display-none" 
   3:         OnClick="SimulateEnterForLogin" />
   4:     <sm:LoginControl id="userLoginControl" runat="server" ... />
   5: </asp:Panel>

The cool thing is that I also had the register control on the same page. I plopped a <asp:Panel /> around that and bamn! The enter key Raised the appropriate server-side event depending on what I was doing. Logging in or registering.

Anyway, I hope this helps someone.

-T

 

Ever wanted to handle your ASP.NET AJAX errors a little bit cooler? Here's a little something you can use to add a bit more value to your ASP.NET AJAX applications and its very simple to add to your applications.

 

To test it out, you'll want to add a PageMethod or WebMethod.

function startAjaxRequest()

{

PageMethods.MyPageMethod(onSuccess, onError, 'my context');

}

 

Inside that method throw an error. I created a good ole divide by zero exception.

 

Next thing you'll want to do is write the OnError method:

 

function onError(error)

{

var errorControl = new Societymedia.AjaxErrorControl(error,'ajax-error');

errorControl.Render();

}

 

Pass in the error that gets returned from the server and pass in the container element from the page.

 

All that's needed is the .js file, a div and 2 lines of code. Try this out and let me know what you think. All that's left is some styling.

 

Heres the .js file

 

// global variables

var _toggled = false;

 

// global methods

function Societymedia$AjaxErrorControl$ToggleDetails()

{

var __ajaxdetails = $get('__ajaxdetails');

if(!_toggled){

__ajaxdetails.style.display = '';

_toggled = true;

}

else{

__ajaxdetails.style.display = 'none';

_toggled = false;

}

}

 

// register namespace

Type.registerNamespace('Societymedia');

 

// constructor

Societymedia.AjaxErrorControl = function(error, element)

{

///<summary>

///AjaxErrorControl

///</summary>

///<param name="error" type="Object">

///The Error that gets returned from the server

///</param>

///<param name="element" type="string">

///The id of an element on the page which will host the AjaxErrorControl

///</param>

this._error = error;

this._domElement = $get(element);

if(this._domElement == null)

{

alert('Opps! Looks like you forgot to include an element on your page with an id of ' + element + '.');

throw Error.argumentUndefined();

}

this._removeNodes(this._domElement);

_toggled = false;

}

// prototype

Societymedia.AjaxErrorControl.prototype =

{

Render : function()

{

///<summary>

/// The Render Method will use error object passed in to the ctor to fill the asp.net error template

///</summary>

 

// create local variables that will be string.format() in the template

var errorMessage = this._error.get_message();

var statusCode = this._error.get_statusCode();

var stackTrace = this._error.get_stackTrace();

var exceptionType = this._error.get_exceptionType();

 

// create the template

var template = new Sys.StringBuilder();

template.append('<h1>Server Error in \'{0}\' Application.</h1>');

template.append('<hr />');

template.append('<h2><em>{1}</em></h2>');

template.append('<p><strong>Description:</strong> An unhandled exception occurred during the execution of the current AJAX request. Please review the stack trace for more information about the error and where it originated in the code.</p>');

template.append('<p><strong>Exception Details:</strong> {2}: {1}</p>');

template.append('<p><strong>Source File:</strong> {3}</p>');

template.append('<p><strong>Stack Trace:</strong></p>');

template.append('<p>{4}</p>');

 

var formattedTemplate = String.format(template.toString(),document.location.pathname, errorMessage, exceptionType, document.URL, stackTrace);

 

 

var errorText = '<p>Opps! An error has occured during the execution of the current AJAX request.</p><div>For a detailed description of the error, please click the button</div><p>We\'re Sorry for the inconvenience.</p>';

var divErrorText = this._createElement('div');

this._setText(divErrorText, errorText);

this._domElement.appendChild(divErrorText);

 

 

 

var detailsButton = this._createElement('input');

detailsButton.setAttribute('id', '__ajaxdetailsbutton');

detailsButton.onclick = Societymedia$AjaxErrorControl$ToggleDetails;

detailsButton.setAttribute('value', 'Details');

detailsButton.setAttribute('type', 'button');

this._domElement.appendChild(detailsButton);

 

var divFormatted = this._createElement('div');

divFormatted.setAttribute('id', '__ajaxdetails');

divFormatted.style.display = 'none';

this._setText(divFormatted, formattedTemplate);

this._domElement.appendChild(divFormatted);

 

 

},

 

_createElement:function(type)

{

///<summary>

///creates an element of type

///</summary>

///<param name="type" type="string">

///is this the type we want to return

///</param>

return document.createElement(type);

},

 

_removeNodes:function(element)

{

///<summary>

///Removes all children inside the element

///</summary>

///<param name="element" type="HTMLDOMElement">

///this is the HTMLDOMElement we want to add text to

///</param>

for(var i=element.childNodes.length-1;i>=0;i--)

{

element.removeChild(element.childNodes[i]);

}

},

 

_setText:function(element,value)

{

///<summary>

///Basically just innerHTML'ing the value to the element

///</summary>

///<param name="element" type="HTMLDOMElement">

///this is the HTMLDOMElement we want to add text to

///</param>

///<param name="value" type="string">

///The Text to be added to the HTMLDOMElement

///</param>

element.innerHTML = value;

}

 

}

 

 

 

Societymedia.AjaxErrorControl.registerClass('Societymedia.AjaxErrorControl');

 

 

 

 

 

 

 

 

 

When I first started learning ASP.NET Ajax I was a bit stumped on Client Life cycle.  I didn't quite "get it". I mean, what's the deal. There was the pageLoad() and pageUnload() methods that got called without having to be "wired up" and then the Initialize/Begin/End request events. And there were these other page_loading/page_loaded events? What was the difference? Also where did the windows onload and unload events sit in the asp.net AJAX client life cycle. This blog shall serve as an ongoing post about what I have uncovered now and what I'll continue to uncover as I get into more asp.net AJAX.

   

I "think" I have figured out how all three "objects " are pieced together and I'm quite impressed with this puzzle. Its doesn't take long to realize Microsoft's intentions - bring the familiar Server-Side model to the client.

   

First off. Maybe its just me (it probably is because I'm retarded) but the ASP.NET AJAX documentation just didn't cut it. Its pretty good at being a reference but it's not really a good starting point to learn ASP.NET AJAX but that's all I found at first. After googling my issues I ran into some AMAZING ASP.NET AJAX folks. Here, Here, Here, Here and of course Here. Along with my pisan Dino Esposito and the man I'm probably the most jealous of Rick Strahl, GAWD I HATE YOU :).  They are my ASP.NET AJAX heroes. lol. I know. I really need to get out more. So lets see if I can shed some light and figure out on which came first, the Application or the PageRequestManager or the window?

   

Another question I had was about the pageLoad() and pageUnload(). How do these events get wired up and called automicrosoftly? I suppose in hindsight its obvious but it wasn't obvious to me at first.

   

Before I show you the answer, let me start by talking about the Application and the PageRequestManager. Both of these objects raise events that the client framework exposes for us to handle. And the events are very similar in name. pageLoad, pageLoading, pageLoaded.

   

The way I like to help differentiate them is by thinking about the PageRequestManager classes in terms of doing the real heavy AJAX lifting when you use that bastardly UpdatePanel.

   

Now as far as I can tell, these events are only called while doing an asynchronous post back via an update panel. If you're doing creating the AJAX calls via PageMethods or ScriptServices, these events will not fire and you need to do whatever you need to do, hide/show progress bar or DOM manipulating, between the AJAX Call and your client-side event handler. The only event that will fire is the PageLoaded.

   

Application Events:

These events can be found under Sys.Application.

   

  • Init - This event gets raised when the page is first rendered after all scripts have been loaded.
  • Load - This event gets raised after all scripts have been loaded AND all objects on the page have been created and initialized.
  • Unload - This event get raised before all the objects on the page have been disposed.

   

Incidentally, do any of these events sound familiar?

   

PageRequestManager Events:

These events can be found under Sys.WebForms.PageRequestManager.

   

  • InitializeRequest - This event is raised at the beginning of an AJAX call.
  • BeginRequest - This event gets raised just before the AJAX call is sent to the server to be processed. Its very common to start some sort of AJAX progress graphic. I will show an example of something I've done here in a later post (UpdatePanel only)
  • PageLoading - This event gets raised when the AJAX result comes back from the server.
  • PageLoaded - This event is next and gets raised just after the AJAX result have been processed.
  • EndRequest - This event gets raised at the end of an AJAX request. Its common to handle server 500 errors in this event. I will show you an example of this as well.

   

Window Events:

As web developers we should all know these events .

   

  • onload - This event gets fired by the window object after the page has fully loaded
  • onunload - this event gets fired by the window object just before the page gets disposed of. Either by leaving the page or reloading the current page.

(Of course there are more but im only showing these two)

   

PageRequestManager AND Application

When I first started on my AJAX studies the first thing kept stumbling over was the Application and PageRequestManager. What was the difference between the two models and when should I use one over the other. Now that we know what each event does lets try and cement the differences between them.

   

The PageRequestManager is really the brains of the UpdatePanel. The only time I really wire up the events of the PageRequestManager is when I am using an update Panel. Its in these events that I added progress indicators or custom error handling (Examples soon to come). The only event that will be called outside of an UpdatePanel is the PageLoaded.

   

The Application object is really what gives you the initial hook into the ASP.NET AJAX framework much like the Page_Init and Page_Load are in the Server-Side Model. Application object is really the brains behind the ASP.NET AJAX framework providing many types of ApplicationServices including Profile, Authentication and even Globalization/Localization. The Application Object is to the client as the Page Object is to the Server. In fact, as soon as the Page object is finished doing its magic, it hands control over to the client as a global variable called Sys.Application.

   

Page_Load and Page_Unload

One of the first things I wondered when I was learning AJAX was how are these events:

function pageLoad(sender, eventArgs){}

function pageUnload(sender, eventArgs){}

   

Called without wiring them up? The answer? The Window object of course. When the page finishes loading, it fires the window.onload event which is caught and handled by the ASP.NET AJAX framework which kick starts the ASP.NET AJAX framework into motion. The framework looks to see if there is a function called pageLoad if there is one, it calls it. Same holds true for pageUnload event.

   

Conclusion

I hope this beginning ASP.NET Ajax article sheds some light on the client-side framework provided to us by Microsoft. As I learn more about this framework I'll be writing more and more.

   

Thanks.

   

   

Congratulations to me! I'm finally getting off my butt and comitting to becoming the best possible developer I can be. Part of that journey starts here, with this blog. I fully intend to both reach out to the community and reach within myself to do everything within my power to improve myself and those around me. This blog will serve as a vessel for my technical thoughts as well as my personal ones.

Why am i starting a blog? Well, first and foremost, I suck at writing and communicating in general. This is one of the areas in which I MUST improve if i'm going to advance my career in software development. Another reason for starting my blog is not because i want to share my vast knowledge of the .net framework or lack thereof but to get more involved in the community.

I love asp.net and all web technologies and I'm passionate about becoming the best i can be. Its a pledge to myself. So cheers to me and i hope to meet many new friends, peers, and mentors.

-Tony 

More Posts