Showing posts with label AJAX. Show all posts
Showing posts with label AJAX. Show all posts

Tuesday, November 17, 2009

How to use the Ajax Toolkit AutoCompleteExtender

I can never seem to remember the basics of how to use the AJAX Toolkit AutoCompleteExtender. It isn’t that it is all that difficult, it is just that there are things that I forget. The sample page is actually quite useful.

Step 1: Create a Web Service that you can call.

First be sure to uncomment or add the ScriptService attribute to your web service. It should look like this and be above the class declaration.

[System.Web.Script.Services.ScriptService]

The second thing to know is when you create your web method you MUST use very specific parameters for your web method. The one exception (sort of) is if you use the ContextKey property, but even then you are only adding a parameter. The name of the method is NOT important because you specify that in the extender properties. Your method must have two parameters named EXACTLY as shown below and in the order below. The return type can be string[] or List<string> because they both end up as string[] when sent as xml.

[WebMethod]
public List<string> GetEmails(string prefixText, int count)
{

}

Step 2: Add the Extender to the page or user control where you want to use it.

I would start by pasting the below on your page or user control.

<ajaxToolkit:AutoCompleteExtender
runat="server"
ID="myAutoComplete"
TargetControlID="txtSomeField"
ServicePath="~/MyWebService.asmx"
ServiceMethod="GetEmails"
MinimumPrefixLength="3"
CompletionInterval="300"
EnableCaching="true"
CompletionSetCount="50"
DelimiterCharacters=";, :" />
Then paste the following under your <%@ Control… > or <%@ Page …> tags (see the first line in the file).
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="ajaxToolkit" %>
You will notice that my example doesn’t contain any animations, javascript, etc. I personally like it with no animation. The default seems zippier when I type. It also has the big advantage of making it very simple to put this in a user control and have that user control used multiple times on a page without issue. You can apparently still get it to work, but I don’t think it is worth it. Check out the solution posted here to see how to make it work. The sample shown on the sample page provided with the toolkit just won’t work if you use the extender multiple times on a page.

You may also notice I removed the stylessheet references, and the BehaviorID (which is in the sample project, but not in the documentation) is also not there. You do NOT want to set this if you use this multiple times on a page.

You can tweak the other parameters if you like. The ~ in the ServicePath only works for Web Sites, not Web Applications from what I understand. Please let me know if I am wrong since I have not actually done so. I did find that I needed the ~ if everthing is not at the top level in the web site. For example, I used the extender on a user control that is in a Controls directory under the root. The MyWebService.asmx is a the top root, so some kind path is needed. http://….MyWebService.asmx will also work, but that is difficult to make production and dev difficult to work with.

Step 3: Add ScriptManager

You will need either the ToolkitScriptManager or the ScriptManager on the page or a master page, etc for any AJAX stuff to work. Just remember, you can only have one of these on a page when the page is rendered. That means you can’t have it on a master page and on a user control.

Tuesday, April 28, 2009

AJAX ReorderList breaks when using EntityDataSource entity that has a Navigation Property

After some digging, I figured out the reason why the ReorderList from the AJAX Control Toolkit stopped working for me. When I use the ReorderList with the SqlDataSource it works fine. When I use it with the EntityDataSource it works also. Well sort of. It works fine if the object that you are binding to do not have a Navigation Property in ADO.NET Entity Data Model.

If the object does, you will not receive an error when you reorder items in the ReorderList, but it will not work either. The reason is that the control is not completely robust / completed. If figured this out by changing my reference to the the AjaxControlToolkit.dll that is in the source code version of the Ajax Control Toolkit Sample Application. This allowed me to step through the code. There I saw code that “swallowed” the exception and thus never reported it to the calling method. This is why there is no error, but it is not working either.

Here is the InnerException that I found when I stepped through the ReorderList code:

"Error while setting property 'ICAContract': 'This property descriptor does not support the SetValue method.'." 

   at System.Web.UI.WebControls.EntityDataSourceUtil.SetAllPropertiesWithVerification(EntityDataSourceWrapper entityWrapper, Dictionary`2 changedProperties, Boolean overwrite) 
   at System.Web.UI.WebControls.EntityDataSourceView.InstantiateEntityFromViewState(EntityDataSourceWrapper entityWrapper, IDictionary mergedKeysAndOldValues) 
   at System.Web.UI.WebControls.EntityDataSourceView.ExecuteUpdate(IDictionary keys, IDictionary values, IDictionary oldValues) 
   at System.Web.UI.DataSourceView.Update(IDictionary keys, IDictionary values, IDictionary oldValues, DataSourceViewOperationCallback callback)

Here is the code from ReorderList.cs file.

try{…}
catch (Exception ex)
{
    System.Diagnostics.Debug.Fail(ex.ToString());
    //TODO WHY ARE SWALLOWING THIS EXCEPTION!!!
}

I have to conclude that ReorderList needs to handle error properly, and that potentially the ADO.NET Entity Framework may need some more work. I can’t confirm that, but I do know that I have had to implement several work arounds as noted in these blog entries, and that the experience has been buggy at best.

Monday, April 27, 2009

AJAX ReorderList Example for Adding and Editing Items using the SqlDataSource

The AJAX Control Toolkit has some very powerful controls in it. The ReorderList is no exception. It basically allows users to drag and drop rows of the list around into any order the user desired. It also, allows the user to edit and add new rows as well.

It does support the SqlDataSource quite well. This blog entry will show how to use the SqlDataSource. However, it seems that does not support the EntityDataSource that well. In a later blog entry I will show how to use the EntityDataSource.

The ReorderList has some undesired behavior such as being able to reorder items when one of the rows is in Edit mode. This “feature” allows the following problem that shows when the items are reordered, after the postback, the same position (EditItemIndex) is the same which means if the item you were editing has a different position, another row (now in that same EditItemIndex) will be edited, not the row you were editing. I show you how to change (fix) this behavior.

 Most examples you find out there, except one that I can find don’t show how to do Edit on the list of items. This blog shows a pretty good implementation of using the SqlDataSource, so I would recommend looking here also. This is where I started and what my examples are based on. The only issue I found with his example is that after an reorder, edit will edit the wrong row. To fix this be sure to do the key extra steps.

There are some key extra steps you need to do to get Editing of the Reorder list to work, that you don’t necessarily have to do for the read only mode.

  1. PostBackOnReorder=”True”
  2. Don’t use the Update panel around the ReorderList (you can, but it won’t do any good).

This example assumes you have a table in your database. Here is the SQL you can use to create one.

CREATE TABLE [dbo].[TestTable1](
    [intID] [int] IDENTITY(1,1) NOT NULL,
    [strName] [varchar](50) NOT NULL,
    [strLink] [varchar](50) NOT NULL,
    [intOrder] [int] NOT NULL,
CONSTRAINT [PK_TestTable1] PRIMARY KEY CLUSTERED
(
    [intID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

This is an enhanced copy of the blog I noted earlier.

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="ajaxToolkit" %>

<!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 id="Head1" runat="server">
    <title>OrderedList AJAX</title>
    
    <style type="text/css">
        .ajaxOrderedList li
        {
            list-style:none;
        }
    </style>
    
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server" ScriptMode="Release" />        
       
        <div class="ajaxOrderedList">
          <ajaxToolkit:ReorderList ID="ReorderList1" runat="server"
            AllowReorder="True"
            PostBackOnReorder="True"
            SortOrderField="intOrder"
            DataKeyField="intID"
            DataSourceID="sqlDSItems"
            ItemInsertLocation="End" 
            onitemreorder="ReorderList1_ItemReorder" 
            onitemcommand="ReorderList1_ItemCommand">
            
                <ItemTemplate>
                    &nbsp;
                    <asp:HyperLink ID="HyperLink1" runat="server" Text='<% #Eval("strName") %>' NavigateUrl='<%# Eval("strLink") %>' />
                    <asp:LinkButton ID="LinkButton1" runat="server" CommandName="Edit" Text="Edit" />
                    <asp:LinkButton ID="LinkButton3" runat="server" CommandName="Delete" Text="Delete" />
                </ItemTemplate>
                
                <DragHandleTemplate>
                    <asp:Panel ID="dragHandle" runat="server" 
                        style="height: 20px; width: 20px; border: solid 1px black; background-color: Red; cursor: pointer;" 
                        Visible="<%# ShowDragHandle %>">
                        &nbsp;
                    </asp:Panel>
                </DragHandleTemplate>
                
                <ReorderTemplate>
                    <div style="width: 300px; height: 20px; border: dotted 2px black;">
                        &nbsp;
                    </div>
                </ReorderTemplate>
                
                <InsertItemTemplate>
                    <asp:Label ID="Label1" runat="server" Text="Name"></asp:Label>
                    <asp:TextBox ID="txtName" runat="server" Text='<%# Bind("strName") %>'></asp:TextBox><br />
                    
                    <asp:Label ID="Label2" runat="server" Text="Link"></asp:Label>
                    <asp:TextBox ID="txtLink" runat="server" Text='<%# Bind("strLink") %>'></asp:TextBox><br />
                    <asp:Button ID="btnInsert" runat="server" Text="Add Link" CommandName="Insert" />
                </InsertItemTemplate>
                
                <EditItemTemplate>
                    <asp:TextBox ID="txtName" runat="server" Text='<%# Bind("strName") %>'/>
                    <asp:TextBox ID="txtLink" runat="server" Text='<%# Bind("strLink") %>' />
                    <asp:TextBox ID="txtOrder" runat="server" Text='<%# Bind("intOrder") %>' />
                    <asp:LinkButton ID="LinkButton1" runat="server" CommandName="Update" Text="Update" />
                    <asp:LinkButton ID="LinkButton2" runat="server" CommandName="Cancel" Text="Cancel" />                      
                </EditItemTemplate>
                
            </ajaxToolkit:ReorderList>
            
            <asp:Label ID="Label3" runat="server" Text="Label"></asp:Label>
            <asp:Button ID="Button1" runat="server" Text="Button" />
        </div>
                
        <asp:SqlDataSource ID="sqlDSItems" runat="server" ConnectionString="<%$ ConnectionStrings:MyConnectionString %>"
                SelectCommand="SELECT [intID], [strName], [strLink], [intOrder] FROM [TestTable1] ORDER BY [intOrder]"
                DeleteCommand="DELETE FROM [TestTable1] WHERE [intID] = @intID"
                InsertCommand="INSERT INTO [TestTable1] ([strName], [strLink], [intOrder]) VALUES (@strName, @strLink, @intOrder)"
                UpdateCommand="UPDATE [TestTable1] SET [strName] = @strName, [strLink] = @strLink, [intOrder] = @intOrder WHERE [intID] = @intID">
            <DeleteParameters>
                <asp:Parameter Name="intID" Type="Int32" />
            </DeleteParameters>
            <UpdateParameters>
                <asp:Parameter Name="strName" Type="String" />
                <asp:Parameter Name="strLink" Type="String" />
                <asp:Parameter Name="intOrder" Type="Int32" />
                <asp:Parameter Name="intID" Type="Int32" />
            </UpdateParameters>
            <InsertParameters>
                <asp:Parameter Name="strName" Type="String" />
                <asp:Parameter Name="strLink" Type="String" />
                <asp:Parameter Name="intOrder" Type="Int32" />
            </InsertParameters>
        </asp:SqlDataSource>
        
    </form>
</body>
</html>

I do however need some code-behind because I am using events to fix some of the issues I described above. Here is the code-behind

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data.SqlClient;
using AjaxControlToolkit;

public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Label3.Text = DateTime.Now.ToLongTimeString();
        if (!IsPostBack)
        {
            ShowDragHandle = true;
        }
    }

    protected void ReorderList1_ItemReorder(object sender, ReorderListItemReorderEventArgs e)
    {
        ShowDragHandle = true;
    }

    protected Boolean ShowDragHandle { get; set; }

    protected void ReorderList1_ItemCommand(object sender, ReorderListCommandEventArgs e)
    {
        switch (e.CommandName)
        {
            case "Cancel":
            case "Insert":
            case "Delete":
            case "Update":
                ShowDragHandle = true;
                break;

            case "Edit":
                ShowDragHandle = false;
                break;
            
            default:
                break;
        }
    }
}

The code is pretty straight forward I think, but here is a bit of explanation to help you understand. The ShowDragHandle boolean is bound to the Panel that makes up the drag handle. It shows and hides based on this boolean. When the page if first loaded (non-postback), it is shown. All events except Edit set the ShowDragHandle to true.

I do have a Label called Label3 that is set to the current data-time on page load. This is there so that you can see when a postback occurs. No real reason to include this in your solution, it is really just to help see when a postback occurs.

Tips

  • Be sure that your select statement has an order by statement and is ordering by (ASCENDING) the same column you set the SortOrderField to.
  • If you need more information on installing the AJAX Control Toolkit in Visual Studio 2008 SP1, check of my blog entry.

Thursday, April 23, 2009

Installing AJAX Control Toolkit in Visual Studio 2008 SP1

I found it a bit frustrating to figure out what I needed and how to setup everything to use the AJAX Control Toolkit. Here is what I learned.

Since ASP.NET AJAX is built into ASP.NET 3.5 and ASP.NET 3.5.1(which is part of Visual Studio 2008 and Visual Studio 2008 SP1 respectively), you don’t need to download or install anything else when usual Visual Studio 2008 SP1, except the Toolkit itself. It is NOT included with any Visual Studio. You MUST download the toolkit itself.

Click here to go to the download page. It is important to get the correct version of the toolkit since it has one for each version of Visual Studio. In particular, there is a version for Visual Studio 2008 (Original Release) and another version for Visual Studio 2008 SP1. To download click one of the four links under the Downloads & Files section on the page. I recommend the AjaxControlToolkit-Framework3.5SP1-NoSource.zip link if you don’t need the source, and just want to use the toolkit. This is the kind of installation you would typically get with any other third party control library.

Figuring that out was the hard part for me. I’m sure there is some document out there that explains all this, but I only found clues in different places.

Now that you have the file, unzip it to a location on your hard drive. You will then need to run the installer. Depending on where you unzipped the file, the installer is located at a path similar to the following:

C:\AjaxControlToolkit-Framework3.5SP1-NoSource\AjaxControlExtender\AjaxControlExtender.vsi

The project template never shows up in Visual Studio, so I don’t know what the installer actually did. I would love to hear what other people experience.

Open Visual Studio 2008 SP1 and add a tab to the Toolbox for the AJAX Toolkit controls by right-clicking on the Toolbox .

Add the toolkit controls to the tab by right-clicking the area below the tab label. Choose the Choose Items…. menu item. Browse to the AjaxControlToolkit.dll assembly.

The DLL for controls that you will use is located in the SampleWebSite\Bin directory. The path should be similar to:

C:\AjaxControlToolkit-Framework3.5SP1-NoSource\SampleWebSite\Bin\AjaxControlToolkit.dll

The AJAX Control Toolkit controls are now available just like any other server control.

NOTE: If you don’t have a project open and the active file (in your main window) is a web page or user control, etc, you won’t see the controls because Visual Studio only shows the controls in the proper context like editing a web page.

References

Wednesday, March 4, 2009

Master-Detail GridViews using AJAX.

Recently, I had the need to have a GridView on the top half of a page, and GridView of related records on the bottom half of the page. This is the typical Master-Detail relationship. When the user clicks on a LinkButton on the Master GridView I want the Detail GridView to update. The trick is, I want this to happen without a postback. AJAX to the rescue.

I have attached an event handler for the RowCommand event of the Master GridView. In here I look for the CommandName that I specified in the TemplateField that has a ButtonLink in it. This allows me to handle the ButtonLink clicks in one place and quite simply.

Well, there are a few tricks I had to learn to get everything to work with AJAX. Here is what I did.

  1. Add the ScriptManager control to the ASP.NET page.
  2. Add the UpdatePanel control to the page.
  3. Set the UpdateMode to Conditional
  4. Move the Detail GridView into the UpdatePanel
  5. Added an event handler for RowCreated event on the Detail GridView.
  6. In the RowCreated event handler I used e.Row.FindControl() to find my ButtonLink on the row.
  7. Once I had the ButtonLink, I registered it using the ScriptManager1.RegisterAsyncPostBackControl(myLink) method to register my ButtonLink.

My failed attempts included trying to register the Master GridView using the ScriptManager1.RegisterAsyncPostBackControl(MyMasterGridView), and at first appeared to work. Then I noticed strange behavior. My column headers would not sort anymore, and another column that had a checkbox in it was firing it changed event for every row. I would love to know why registering the GridView did not work. I suspect it may have something to do with the callback based sorting the GridView can do out of the box.

Making a call to the server from JavaScript using ASP.NET Client Callback (Low Level AJAX)

The concept behind AJAX is really nothing new. The basic idea is that from JavaScript we want to call a server function and have the result returned to us. The result is returned by calling a JavaScript callback function such that the result from the server is sent via a parameter to this JavaScript callback function. Typically we want to display the result dynamically by updating the HTML page we are on without a postback. To do this, the JavaScript callback function typically will change the page using JavaScript.

In ASP.NET, you can use update panels, and other high level methods of doing this, but this is essentially what is going on behind the scene. Sometimes, you may want or need to do the same thing but at a very low level.  

To add this type of functionality to your ASP.NET page, here is a list of simple ways (not the only ways) you can add this low level AJAX to your page.

Make the class for your page implement System.Web.UI.ICallbackEventHandler interface.

This interface requires that you define the following two methods.

   
string result;
    // Define method that processes the callbacks on server.
    // NOTE: This is required by the ICallbackEventHandler interface
    public void RaiseCallbackEvent(String eventArgument)
    {
        result = eventArgument + "!!!!";
    }


    // Define method that returns callback result.
    // NOTE: This is required by the ICallbackEventHandler interface
    public string GetCallbackResult()
    {
        return result;
    }

Since AJAX is asynchronous that means that after the RaiseCallbackEvent method is called that processing can take a long time, and will not block further execution. This means that when the processing is done we need to have some way of knowing that, and then doing something with that result. In our example, I am simply adding "!!!!" to the value, but this could very complex long running code. That is what the GetCallbackResult method is for. It gets called only when the RaiseCallbackEvent method has completed. You don't call it, the framework does this for you.

You may notice that we use a result variable to communicate between the two methods. I think this is a bit strange, and would have expected the result to be passed as an parameter to GetCallbackResult, but it wasn't. :) I expect this approach is nearly as simple, and arguably more flexible.

Now that we have defined what will be happen when the AJAX request is received by the server, we need to define what will happen when data is passed back to the JavaScript page.

For this, let's assume we want to display the results in a span tag on our page. For this let's assume we have something like the following defined somewhere on our page.

<span id="MyMessage">Original Content Here</span>

We need to define one methods to do something with the result once it is sent back to the browser. The result shows itself as a parameter to the JavaScript function. It would be best to define one that will get called if there is an error with the AJAX call.

The JavaScript Function would be something like this.

function DoSomethingCallback(text)
{
  var myMessage = document.getElementById("MyMessage");
  MyMessage.innerHTML = text;
}

function DoSomethingErrorHandler(text)
{
  var myMessage = document.getElementById("MyMessage");
  MyMessage.innerHTML = 'An error has occurred.';
}

 
Now all we need to do is wire up the callback. This is done by asking ASP.NET to create the callback for us. Here is the C# code that must fire on EVERY page load. So, put it in the page load event or some other event that occurs EVERY time the page loads. If you don't do this on every page load, you will get postbacks instead of AJAX calls.

In the method below the first parameter is the page that implements the ICallbackEventHandler. The second parameter is in this case the name of parameter, but it could also be a global JavaScript variable, or a constant. If it is a string constant, you will need to use the embedded single quotes. The third parameter is the name of the JavaScript function we defined above that handles the returned data. The fourth parameter is the name of the JavaScript function we defined above that handles the error. The last parameter is to designate we are doing this asynchronously.

// Get the JavaScript Callback
String jscriptCallback = Page.ClientScript.GetCallbackEventReference(this, "val", "DoSomethingCallback", "",
"DoSomethingCallbackErrorHandler", false);


In theory we are done. You can now use the value of the jscriptCallback C# variable however you want. Now, however you want to get that JavaScript code to the browser is up to you. In this example, I am taking some shortcuts to make understanding the example a bit easier. However, in real life just like other JavaScript stuff in ASP.NET, you would want to use RegisterClientScriptBlock to define the JavaScript function, instead of using a property as I have done below.

For our example, I am adding a property like the following.

public string AjaxJavaScript
{
   get
   {
      // Get the JavaScript Callback
      String jscriptCallback = 
         Page.ClientScript.GetCallbackEventReference(this, "val", 
      "DoSomethingCallback", "",
      "DoSomethingCallbackErrorHandler", false);
      return jscriptCallback;
   }
}

Now I can use this anywhere in my .aspx page to get access to the code. In most cases you will need to wrap this code in a JavaScript function since the code generated by ASP.NET has both single and double quotes. This makes it very difficult to use directly in an event handler that is defined in quotes or double-quotes. Here is the example JavaScript function.

function GetMessageFromServer(val)
{
  <%= AjaxScript %>
}

If you view the source on the page, you would see that the code looks something like the following.

function GetMessageFromServer(val)
{
WebForm_DoCallback('__Page',val,DoSomethingCallback,"",DoSomethingCallbackErrorHandler,false);
}


Notice that the second parameter here is val which must match the parameter of the surrounding function.

Now for the exciting part. Now we can actually call the function from anywhere we can use JavaScript. In the below example, I am calling the JavaScript from two HTML buttons.

<input type="button" value="12345" onclick="GetMessageFromServer(12345)"/>
<input type="button" value="111" onclick="GetMessageFromServer(111)"/>



Here is the complete source for easy copy and paste:


<%@ Page Language="C#" %>
<%@ Implements Interface="System.Web.UI.ICallbackEventHandler" %>


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">


<script runat="server">
   
    string result;
    // Define method that processes the callbacks on server.
    // NOTE: This is required by the ICallbackEventHandler interface
    public void RaiseCallbackEvent(String eventArgument)
    {
        result = eventArgument + "!!!!";
    }


    // Define method that returns callback result.
    // NOTE: This is required by the ICallbackEventHandler interface
    public string GetCallbackResult()
    {
        return result;
    }



public string AjaxJavaScript
{
    get
    {
       // Get the JavaScript Callback
       String jscriptCallback = Page.ClientScript.GetCallbackEventReference(this, "val",
       "DoSomethingCallback", "",
       "DoSomethingCallbackErrorHandler", false);
    return jscriptCallback;
    }
}
   
</script>


 


<html>
<head>
    <title>Low Level AJAX Example</title>
   
    <script type="text/javascript">
function DoSomethingCallback(text)
{
  var myMessage = document.getElementById("MyMessage");
  myMessage.innerHTML = text;
}


function DoSomethingCallbackErrorHandler(text)
{
     var myMessage = document.getElementById("MyMessage");
  myMessage.innerHTML = 'An error has occurred.';
}


function GetMessageFromServer(val)
{
     //Example: WebForm_DoCallback('__Page',val,DoSomethingCallback,"",DoSomethingCallbackErrorHandler,false);
     <%= AjaxJavaScript %>
}


</script>
</head>
<body>
    <form id="Form1" runat="server">
  <span id="MyMessage">Original Content Here</span>
  <input type="button" value="12345" onclick="GetMessageFromServer(12345)"/>
  <input type="button" value="111" onclick="GetMessageFromServer(111)"/>
    </form>
</body>
</html>


Thursday, July 24, 2008

Adding Loading animation to all AJAX calls

So, you like AJAX, and your users like it except they can't tell when something is being done in AJAX. They complain that there is nothing that indicates to them that something is happening such as a query for a list, save, etc. One solution to this is to show an animated gif next to the element that caused the AJAX call in the first place. While the first line below and the BeginRequest() method are ASP.NET AJAX specific, the addLoadingAnimation() method is just JavaScript and can be used with Java, PHP, etc. The addLoadingAnimation() is IE and FireFox compatible on Windows, and has not been tested in any other browsers or platforms. The animation is removed in the endRequest event in ASP.NET when using an Update Panel. There is nothing needed because of how the Update Panel works. In other situations, the animation may need to be removed manually. Additional code would be needed, but should be able to be done in a similar way. The code assumes that the image for the text input will fit nicely inside the text input. A 16 x 16 image will probably work for text inputs of default height. The loading image for all other elements can be whatever size you like.
// Register our event listener that is called when an AJAX request is made.
Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(BeginRequest);

function BeginRequest(sender, args) {
var srcElement = args.get_postBackElement();
addLoadingAnimation(srcElement);
}

// Adds a loading animated gif to the right of
// the element that started an element
// (Except if the element is a text input,
//  then the image is inside the text input
//  as a background image)
function addLoadingAnimation(srcElement)
{

// if element is a textfield, then show the loading image in the textfield itself.
if (srcElement.tagName == "INPUT" && srcElement.type == "text")
{      
   srcElement.style.backgroundImage = 'url(images/loading.gif)';
   srcElement.style.backgroundRepeat = "no-repeat";
   srcElement.style.backgroundPosition = "right";
}

// else the element looks better with the loading image to the right of the element
else
{
   // only add the animation if it isn't there already
   // i.e. user click link twice in a row
   if (srcElement.nextSibling == null
       ||
       (
           !srcElement.nextSibling.innerHTML
          || (
             srcElement.nextSibling.innerHTML
             && srcElement.nextSibling.innerHTML.indexOf("otherLoadingImage") == -1)
             )
      )
   {
      var anim = document.createElement("span");
      anim.innerHTML = '<IMG ID="otherLoadingImage" BORDER="0" SRC="images/loading.gif">';
      srcElement.parentNode.insertBefore(anim, srcElement.nextSibling);
   }
}
}

Tuesday, July 22, 2008

Adding a loading animation to your AJAX requests

I think AJAX is great. It can be a little confusing for the user if the response is not immediate though. In cases like these it is nice to give the user some feedback so that they know that something is happening and when it is done. I like to a little spinning wheel or something to indicate this condition. You can easily add this to most requests that are AJAX based with only a few lines of code.

// register the event listeners so we will receive the events 
//when they are fired by the AJAX.NET framework
Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(BeginRequest);

function BeginRequest(sender, args) {
  // srcElement is the object that invoked the AJAX call. For example, maybe a textfield
  var srcElement = args.get_postBackElement();
 
  // the path should point to a nice animated gif.
  srcElement.style.backgroundImage = 'url(images/loading.gif)';
  srcElement.style.backgroundRepeat = "no-repeat";
  srcElement.style.backgroundPosition = "right";
}

You can use what ever image you want for the animation. I like this one from the AJAX toolkit. It is from the DynamicPopulate Demo