The Disturbed Buddha

Simple Observations of a Self-proclaimed Novice

Executing Server-side Code from JavaScript

In the Asp.net forums, I often see the question asked, “Can I call server-side code from the client/JavaScript?” Almost invariably, the responses given are “No, the client cannot access the server,” or “You can only use WebMethods or PageMethods.” The first response is not entirely correct, and unfortunately, WebMethods and PageMethods are static methods and therefore have no way to directly access the page.

This is why I present to you the following “hack”. I call it a hack because there really should be some way built into the ASP.NET AJAX Extensions that allow this approach directly. Instead, it relies on using controls in a manner that they aren’t necessarily intended in order to obtain the desired result. But this “hack” does have a redeeming quality—it’s incredibly easy.

The Code:

<%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default2.aspx.vb" Inherits="Default2" %>         

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml" > 
<head runat="server"> 
  <title>My Page</title> 
  <script type="text/javascript"> 
    function myClientButton_onclick() { 
    document.getElementById('myServerButton').click(); 
  } 
  </script> 
  <script runat="server"> 
    Protected Sub myServerButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles myServerButton.Click 
      Response.Redirect("http://www.asp.net/") 
    End Sub 
  </script> 
</head> 
<body> 
  <form id="form1" runat="server"> 
    <input id="myClientButton" type="button" value="Press Me" onclick="return myClientButton_onclick()" /> 
    <asp:Button ID="myServerButton" runat="server" style="display:none;" /> 
  </form> 
</body> 
</html>

Explanation:

What I am doing here is adding a server-side button control (<asp:Button runat=”server” …>). I then make the button invisible by adding style=”display:none;”. In the button’s server-side Click event, I do whatever it is I want to do on the server.

In this example, I am clicking a standard client-side button (<input type=”button” …>) to fire off a server-side redirect, but this could just as easily be called by selecting an item in a DropDownList, typing text into a textbox, etc. Clicking the client button calls the JavaScript .click() event of the server-side button control.

The Next Step:

“What if I need to pass arguments from the client-side script to the server-side script?” Well, this is easily done by putting server-side HiddenField controls on the page. Use the JavaScript to set their values (document.getElementById(‘myHiddenField’).value = “Hello World”;) and then use C# or VB.NET to retrieve them on the server (Dim myVar as String = myHiddenField.Value).

In my next article, I will apply this concept to create a rather easy approach to the Ajax Multi-Stage Download Pattern. Check back soon!

Tested on: Internet Explorer 7, Netscape Browser 8, Firefox 2, Safari 3, Opera 9.

Advertisements

January 8, 2008 Posted by | Ajax, ASP.NET, Javascript, Web Development | 23 Comments

Wrapping ASP.NET AJAX TabContainer Tabs

Check out Nazar Rizvi’s article on how to get your tab headers to wrap onto a second row for your ASP.NET AJAX TabContainer control.  Common problem; easy fix.

http://www.narizvi.com/blog/post/Two-rows-of-tab-headers-in-TabContainer-in-Ajax-Control-Toolkit.aspx

December 19, 2007 Posted by | Ajax, ASP.NET, Web Development | 2 Comments

Handling Multiple Asynchronous Postbacks

Sometimes multiple asynchronous postbacks get triggered. Now, I’m not talking about situations where we want to disable a submit button, so that the user doesn’t click it fifty times waiting for something to happen. Instead, I’m referring to situations where we do want each postback to happen in the order it was fired.

However, when a page makes multiple asynchronous postbacks at the same time, the default action is that the PageRequestManager gives the most recent postback precedence. This cancels any prior asynchronous postback requests that have not yet been processed. (Get further explanation.)

So, let’s create a way to “queue up” our asynchronous postback requests and fire them off in order, one by one. First, let’s create an aspx page with three buttons inside of an UpdatePanel:

<asp:ScriptManager ID="ScriptManager1" runat="server" /> 
<asp:UpdatePanel ID="UpdatePanel1" runat="server"> 
    <ContentTemplate> 
        <asp:Button ID="Button1" runat="server" Text="Button" /> 
        <asp:Button ID="Button2" runat="server" Text="Button" /> 
        <asp:Button ID="Button3" runat="server" Text="Button" /> 
    </ContentTemplate> 
</asp:UpdatePanel>

There is no need to wire up any click events in the code-behind for our sample. While you could, all that we are concerned about is that they each cause a postback.

Next, let’s add some deliberate latency into the code so that our postback requests can pile up. Every postback to the server will now take 3 ½ seconds, so that is the fastest each request can be processed.

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load 
  System.Threading.Thread.Sleep(3500) 
End Sub

Now, let’s look at the JavaScript code that will manage the backed-up postback requests for us. Add this script block after the ScriptManager on the page but before the closing </html> tag.

<script type="text/javascript"> 
    var prm = Sys.WebForms.PageRequestManager.getInstance(); 
    prm.add_initializeRequest(InitializeRequestHandler); 
    prm.add_endRequest(EndRequestHandler);        

    var pbQueue = new Array(); 
    var argsQueue = new Array();       

    function InitializeRequestHandler(sender, args) { 
        if (prm.get_isInAsyncPostBack()) { 
            args.set_cancel(true); 
            pbQueue.push(args.get_postBackElement().id); 
            argsQueue.push(document.forms[0].__EVENTARGUMENT.value); 
        } 
    }       

    function EndRequestHandler(sender, args) { 
        if (pbQueue.length > 0) { 
            __doPostBack(pbQueue.shift(), argsQueue.shift()); 
        } 
    } 
</script>

The Code in Detail

First, we use the PageRequestManager to set up handlers for the beginning and end of each asynchronous request:

var prm = Sys.WebForms.PageRequestManager.getInstance(); 
prm.add_initializeRequest(InitializeRequestHandler); 
prm.add_endRequest(EndRequestHandler);

Queuing up the Postbacks…

Then we create an array to store the originator of each asynchronous postback that cannot be processed immediately, as well as an array to store any event arguments associated with the postback:

var pbQueue = new Array(); 
var argsQueue = new Array();

Then, at the beginning of each asynchronous postback, we check to see if the page is already in an asynchronous postback:

function InitializeRequestHandler(sender, args) { 
    if (prm.get_isInAsyncPostBack()) {...}

If it is, we cancel the new postback request, and instead, add the event target and arguments to our arrays:

args.set_cancel(true); 
pbQueue.push(args.get_postBackElement().id); 
argsQue.push(document.forms[0].__EVENTARGUMENT.value);

…and Executing Them

After each asynchronous postback completes, we check to see if there are any more queued up, and if so, we do a __doPostBack(). pbQueue.shift() pulls the first item out of the array and removes it.

function EndRequestHandler(sender, args) { 
    if (pbQueue.length > 0) { 
        __doPostBack(pbQueue.shift(), argsQueue.shift()); 
    } 
}

And that’s it. Run the page, and randomly click some buttons! If you watch the browser’s status bar, you’ll see the asynchronous postbacks piling up. Then, every 3 ½ seconds, you’ll see one of them being processed! (Remember, the 3 ½ seconds is just an arbitrary time that we added into this demonstration, and it has nothing to do with how the code really works.)

Note: If for some reason, you wanted to execute the asynchronous postbacks in reverse chronological order (i.e., the most recent requests get processed first), just replace the array.shift() command in the EndRequestHandler() with array.pop().

December 12, 2007 Posted by | Ajax, ASP.NET, Javascript, Web Development | 10 Comments

Disabling a Trigger Control During Asynchronous PostBack

Often, we want to disable the control that triggered an asynchronous postback until the postback has completed. This prohibites the user from triggering another postback until the current one is complete.

The Code

First add a ScriptManager to the page, immediately following the <form> tag.

<asp:ScriptManager ID="ScriptManager1" runat="server" />

Then add a Label wrapped in an UpdatePanel. This label will be populated with the date and time on each postback. We’ll also add a Button inside of the UpdatePanel to cause the postback.

<asp:UpdatePanel ID="UpdatePanel1" runat="server"> 
    <ContentTemplate> 
        <asp:Label ID="Label1" runat="server" Text="Label" /><br /> 
        <asp:Button ID="Button1" runat="server" Text="Update Time" OnClick="Button1_Click" /> 
    </ContentTemplate> 
</asp:UpdatePanel>

We’ll also add an UpdateProgress control and associate it with our UpdatePanel just to let the user know that something’s happening.

<asp:UpdateProgress ID="UpdateProgress1" runat="server" AssociatedUpdatePanelID="UpdatePanel1"> 
    <ProgressTemplate> 
        Loading... 
    </ProgressTemplate> 
</asp:UpdateProgress>

Next, we’ll add a events in the code-behind to populate the Label and to introduce some latency, simulating a lengthy update to the page.

VB.NET:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load 
    Label1.Text = Now.ToString 
End Sub
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) 
    System.Threading.Thread.Sleep(4000)  'Pause for 4 seconds. 
End Sub

And finally we’ll add the client-side code to disable our button during the postback. The Javascript disables the button during the beginRequest event of the PageRequestManager and enables it when control has been returned to the browser in the endRequest event. The control causing the postback is returned from the get_postBackElement() method of the BeginRequestEventArgs object which is passed to the function handling the beginRequest event.

Add the follow script after the ScriptManager on the page:

<script type="text/javascript"> 
    var pbControl = null; 
    var prm = Sys.WebForms.PageRequestManager.getInstance(); 
    prm.add_beginRequest(BeginRequestHandler); 
    prm.add_endRequest(EndRequestHandler); 
    function BeginRequestHandler(sender, args) { 
        pbControl = args.get_postBackElement();  //the control causing the postback 
        pbControl.disabled = true; 
    } 
    function EndRequestHandler(sender, args) { 
        pbControl.disabled = false; 
        pbControl = null; 
    } 
</script>

And that’s it!

The Complete Source Code:

<%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="_Default" %> 
<%@ Register Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
    Namespace="System.Web.UI" TagPrefix="asp" %> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<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" /> 
            <script type="text/javascript"> 
                var pbControl = null; 
                var prm = Sys.WebForms.PageRequestManager.getInstance(); 
                prm.add_beginRequest(BeginRequestHandler); 
                prm.add_endRequest(EndRequestHandler); 
                function BeginRequestHandler(sender, args) { 
                    pbControl = args.get_postBackElement();  //the control causing the postback 
                    pbControl.disabled = true; 
                } 
                function EndRequestHandler(sender, args) { 
                    pbControl.disabled = false; 
                    pbControl = null; 
                } 
            </script> 
            <asp:UpdatePanel ID="UpdatePanel1" runat="server"> 
                <ContentTemplate> 
                    <asp:Label ID="Label1" runat="server" Text="Label" /><br /> 
                    <asp:Button ID="Button1" runat="server" Text="Update Time" OnClick="Button1_Click" /> 
                </ContentTemplate> 
            </asp:UpdatePanel> 
            <asp:UpdateProgress ID="UpdateProgress1" runat="server" AssociatedUpdatePanelID="UpdatePanel1"> 
                <ProgressTemplate> 
                    Loading... 
                </ProgressTemplate> 
            </asp:UpdateProgress> 
        </form> 
    </body> 
</html>

December 10, 2007 Posted by | Ajax, ASP.NET, Javascript, Web Development | 23 Comments

Maintain Scroll Position after Asynchronous Postback

Do you want to maintain the scroll position of a GridView, Div, Panel, or whatever that is inside of an UpdatePanel after an asynchronous postback?  Normally, if the updatepanel posts back, the item will scroll back to the top because it has been reloaded.  What you need to do is “remember” where the item was scrolled to and jump back to there after the postback.  Place the following script after the ScriptManager on your page.  And since the _endRequest event of the PageRequestManager happens before the page is rendered, you’ll never even see your item move!

<script type="text/javascript"> 
    var xPos, yPos; 
    var prm = Sys.WebForms.PageRequestManager.getInstance(); 
    prm.add_beginRequest(BeginRequestHandler); 
    prm.add_endRequest(EndRequestHandler); 
    function BeginRequestHandler(sender, args) { 
        xPos = $get('scrollDiv').scrollLeft; 
        yPos = $get('scrollDiv').scrollTop; 
    } 
    function EndRequestHandler(sender, args) { 
        $get('scrollDiv').scrollLeft = xPos; 
        $get('scrollDiv').scrollTop = yPos; 
    } 
</script>

December 3, 2007 Posted by | Ajax, ASP.NET, Javascript, Web Development | 35 Comments

Selecting an AJAX AccordionPane by ID

Typically, if we want to programmatically select a particular AccordionPane within an ASP.NET AJAX Accordion control, we set the SelectedIndex property.  This is great if we know the exact order of our AccordionPanes at all times, but this isn’t always so.  Let’s say, for instance, that you have four panes (making their indices 0, 1, 2, and 3, respectively).  If we make the third one (index 2) invisible, now the fourth one has an index of 2.  So let’s create a method to select the AccordionPane by its ID instead.

VB.NET

Public Sub SetPane(ByVal acc As AjaxControlToolkit.Accordion, ByVal PaneID As String) 
    Dim Index As Integer = 0 
    For Each pane As AjaxControlToolkit.AccordionPane In acc.Panes 
        If (pane.Visible = True) Then 
            If (pane.ID = PaneID) Then 
                acc.SelectedIndex = Index 
                Exit For 
            End If 
            Index += 1 
        End If 
    Next 
End Sub

C#

protected void SetPane(AjaxControlToolkit.Accordion acc, string PaneID) { 
    int Index = 0; 
    foreach (AjaxControlToolkit.AccordionPane pane in acc.Panes) { 
        if (pane.Visible == true) { 
            if (pane.ID == PaneID) { 
                acc.SelectedIndex = Index; 
                break; 
            } 
            Index++; 
        } 
    } 
}

It is interesting to note that I attempted to create a client-side equivalent of this method.  However, the ID is not passed down from the server to the client-side behavior, making it impossible to access that propery from the client. 

November 30, 2007 Posted by | Ajax, ASP.NET, Web Development | 10 Comments

View Source Trick for Pages with Partial Rendering

Many people when developing want to look at the browser’s View Source to make sure that things are being outputted correctly or to see where in the DOM certain things are showing up. However, if you are using the Ajax concept of partial rendering (usually via UpdatePanels), what you get is the initial state of the page when it was first loaded before any partial page updates, not the state of the page as it is currently.

To see the “current” source for the page, simply paste the following code into your browser’s location bar. It’s a little long, but it covers most browsers.

javascript:if (typeof(window.document.body.outerHTML) != 'undefined'){'<xmp>'+window.document.body.outerHTML+'</xmp>'} else if (typeof(document.getElementsByTagName('html')[0].innerHTML) != 'undefined'){ '<xmp>'+document.getElementsByTagName('html')[0].innerHTML+'</xmp>'} else if (typeof(window.document.documentElement.outerHTML) != 'undefined'){ '<xmp>'+window.document.documentElement.outerHTML+'</xmp>'} else { alert('Your browser does not support this.') }

November 27, 2007 Posted by | Ajax, ASP.NET, Web Development | 1 Comment

Controlling the ASP.NET Timer Control with JavaScript

Have you ever wanted to control your <asp:Timer> control from client-side code?

Let’s say you’ve named your timer ‘Timer1’. The first step is to create a reference to this component:

    var timer = Sys.Application.findComponent(‘<%= Timer1.ClientID %>’);

Or, better yet, use the $find() shortcut:

    var timer = $find(‘<%= Timer1.ClientID %>’);

You can then easily access the timer’s interval and enabled properties, as well as start and stop the timer.

    //returns the timer’s interval in milliseconds: 
    var waitTime = timer.get_interval;       

    //sets the timer’s interval to 5000 milliseconds (or 5 seconds): 
    timer.set_interval(5000);       

    //returns whether or not the timer is enabled: 
    var isTimerEnabled = timer.get_enabled();       

    //disables the timer: 
    timer.set_enabled(false);       

    //starts the timer: 
    timer._startTimer();       

    //stops the timer: 
    timer._stopTimer();

For the more adventurous of you who would like to look at the client-side behavior code for the Timer control and who elected to download the source code for the AJAX Control Toolkit, you can probably find the .js file at:

Drive:\Program Files\Microsoft ASP.NET\AJAX Control Toolkit\AJAXControlToolkit\Compat\Timer\Timer.js

(If you don’t know what you are doing, do not make any changes to this file!)

November 26, 2007 Posted by | Ajax, ASP.NET, Javascript, Web Development | 8 Comments

A Client-side Ajax Login for ASP.NET

A question was posed on the ASP.NET forums recently asking how to have a login control that doesn’t refresh the page.  The ideal solution would be to just drop an ASP.NET Login control inside an updatepanel.  However, the Login control (along with PasswordRecovery, ChangePassword, and CreateUserWizard controls whose contents have not been converted to editable templates) is not supported inside an UpdatePanel.  They are just not compatible with partial-page updates.

The ASP.NET AJAX Library does include a proxy class that allows client-side authentication, the Sys.Services.AuthenticationService Class!  We can therefore create a very simple and straight-forward “roll-your-own” login solution that does not require a full postback.

Getting Started:

I am going to assume that you have completed all of the normal, necessary steps to allow authentication, but the authentication service is not enabled by default.  You must enable it in the web.config file.  Add the following to your web.config within the <configuration> section:

  <system.web.extensions> 
    <scripting> 
      <webServices> 
        <authenticationService enabled="true" requireSSL="false"/> 
      </webServices> 
    </scripting> 
  </system.web.extensions>

Next, create two DIVs on your page:  one for the “anonymous view” and one for the “logged-in view”.  Set the the display for both DIVs to ‘none’.

  <div id="AnonymousView" style="display: none;"> 
      <input id="txtUsername" type="text" /><br /> 
      <input id="pwdPassword" type="password" /><br /> 
      <input id="chkRememberMe" type="checkbox" />Remember Me<br /> 
      <input id="btnLogIn" type="button" value="Log In" /> 
  </div> 
  <div id="LoggedInView" style="display: none;"> 
      Logged in.<br /> 
      <input id="btnLogOut" type="button" value="Log Out" /> 
  </div>

The get_isLoggedIn() property of the class then allows you to show and hide the DIVs appropriately.

  var ssa = Sys.Services.AuthenticationService; 
  if (ssa.get_isLoggedIn()) { 
      $get('LoggedInView').style.display = ''; 
  } else { 
      $get('AnonymousView').style.display = ''; 
  }

The Complete Code:

  <body> 
    <form id="form1" runat="server"> 
        <asp:ScriptManager ID="ScriptManager1" runat="server" /> 
        <div id="AnonymousView" style="display: none;"> 
            <input id="txtUsername" type="text" /><br /> 
            <input id="pwdPassword" type="password" /><br /> 
            <input id="chkRememberMe" type="checkbox" />Remember Me<br /> 
            <input id="btnLogIn" type="button" value="Log In" /> 
        </div> 
        <div id="LoggedInView" style="display: none;"> 
            Logged in.<br /> 
            <input id="btnLogOut" type="button" value="Log Out" /> 
        </div> 
    </form> 
</body> 
        <script type="text/javascript"> 
            // Hook up the click events of the log in and log out buttons. 
            $addHandler($get('btnLogIn'), 'click', loginHandler); 
            $addHandler($get('btnLogOut'), 'click', logoutHandler); 
            var ssa = Sys.Services.AuthenticationService; 
            if (ssa.get_isLoggedIn()) { 
                $get('LoggedInView').style.display = ''; 
            } else { 
                $get('AnonymousView').style.display = ''; 
            } 
            
            function loginHandler() { 
                var username = $get('txtUsername').value; 
                var password = $get('pwdPassword').value; 
                var isPersistent = $get('chkRememberMe').checked; 
                var customInfo = null; 
                var redirectUrl = null; 
                // Log them in. 
                ssa.login(username, 
                          password, 
                          isPersistent, 
                          customInfo, 
                          redirectUrl, 
                          onLoginComplete, 
                          onError); 
            } 
            
            function logoutHandler() { 
                // Log them out. 
                var redirectUrl = null; 
                var userContext = null; 
                ssa.logout(redirectUrl, 
                           onLogoutComplete, 
                           onError, 
                           userContext); 
            } 
            
            function onLoginComplete(result, context, methodName) { 
                // Logged in.  Hide the anonymous view. 
                $get('LoggedInView').style.display = ''; 
                $get('AnonymousView').style.display = 'none'; 
            } 
            
            function onLogoutComplete(result, context, methodName) { 
                // Logged out.  Hide the logged in view. 
                $get('LoggedInView').style.display = 'none'; 
                $get('AnonymousView').style.display = ''; 
            } 
            
            function onError(error, context, methodName) { 
                alert(error.get_message()); 
            } 
          
        </script>

Comments and Possible Enhancements:

  • There will always be a page refresh on logout.  This is necessary to ensure that any user-specific information is cleared from the page.
  • You can place the two DIVs inside a third DIV and style that, thus showing a consistant style (width, height, border, etc.) for both child DIVs.
  • You will have probably noticed that there is a redirectUrl parameter for both the login() and logout() methods.  This, along with querystring parameters, could easily be adapted to create a login page that users are redirected to for authentication and redirected from once logged in.

References:

http://asp.net/ajax/documentation/live/ClientReference/Sys.Services/AuthenticationServiceClass/default.aspx

November 19, 2007 Posted by | Ajax, ASP.NET, Javascript, Web Development | Leave a comment

Welcome!

This will (hopefully) be the first of many posts primarily focusing on Ajax, ASP.NET, and general web development.  I am not an expert by any means, but I do enjoy helping others.  I try to answer questions on www.asp.net’s forums whenever I can, and I often see the same questions come up over and over.  Therefore, whenever I come upon something interesting, helpful, or particularly sparsely documented, I will post it here. 

And as I said, I’m not an expert, so I’m always open to comment and suggestions!  I look forward to hearing from you.

November 16, 2007 Posted by | Ajax, ASP.NET, Web Development | , , | 1 Comment