Damian Mehers' Blog Evernote and Wearable devices. All opinions my own.

22Oct/080

ASP.NET MVC with the AJAX Control Toolkit: automatically getting control dependencies

[Edited 23rd Oct to compress returned JavaScript]

I recently wrote a blog post showing how you can use controls from the AJAX Control Toolkit in ASP.NET MVC applications, specifically the ListSearchExtender.  Stephen Walther also wrote one on using the Calendar control here.

One thing that bugged me was that it was painful to find out what scripts a particular control depended on, so that they could be included in the JavaScript used to initialize a control.

I decided to create a simple MVC Controller/View that would return back all the JavaScript required for any particular ASP.NET AJAX Control Toolkit control.

This is the old code for using the ListSearchExtender in an ASP.NET MVC View:

<select id="Countries">
    <option>Switzerland</option>
    <option>United Kindom</option>
    <option>United States</option>
</select>

<script src="/Scripts/MicrosoftAjax.debug.js" type="text/javascript"></script>
<script src="/Scripts/AjaxControlToolkit.Common.Common.js" type="text/javascript"></script>
<script src="/Scripts/AjaxControlToolkit.ExtenderBase.BaseScripts.js" type="text/javascript"></script>
<script src="/Scripts/AjaxControlToolkit.DynamicPopulate.DynamicPopulateBehavior.js" type="text/javascript"></script>
<script src="/Scripts/AjaxControlToolkit.Compat.Timer.Timer.js" type="text/javascript"></script>
<script src="/Scripts/AjaxControlToolkit.Animation.Animations.js" type="text/javascript"></script>
<script src="/Scripts/AjaxControlToolkit.Animation.AnimationBehavior.js" type="text/javascript"></script>
<script src="/Scripts/AjaxControlToolkit.PopupExtender.PopupBehavior.js" type="text/javascript"></script>
<script src="/Scripts/AjaxControlToolkit.PopupControl.PopupControlBehavior.js" type="text/javascript"></script>
<script src="/Scripts/AjaxControlToolkit.ListSearch.ListSearchBehavior.js" type="text/javascript"></script>

<script type="text/javascript">
Sys.Application.initialize();
Sys.Application.add_init(function() {
    $create(AjaxControlToolkit.ListSearchBehavior,
        { "id": "ListBox1_ListSearchExtender" },
        null, null, $get("Countries"));
});
</script>

This is the new code to pull in the dependencies:

<select id="Countries">
    <option>Switzerland</option>
    <option>United Kindom</option>
    <option>United States</option>
</select>

<script src="/ControlDependencies/Get?extenderTypeName=AjaxControlToolkit.ListSearchExtender" type="text/javascript"></script>

<script type="text/javascript">
Sys.Application.initialize();
Sys.Application.add_init(function() {
    $create(AjaxControlToolkit.ListSearchBehavior,
        { "id": "ListBox1_ListSearchExtender" },
        null, null, $get("Countries"));
});
</script>

You'll see that all the individual script includes have been replaced by a single call to the Get action on the ControlDependencies controller.

This is the source to the controller:

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Web.Mvc;
using AjaxControlToolkit;

namespace TestDependencies.Controllers {
public class ControlDependenciesController : Controller {
    readonly Assembly toolkitAssembly =
        typeof(AjaxControlToolkit.Utility).Assembly;

    [OutputCache(VaryByParam = "extenderTypeName",
                 Duration = 86400,  // One day
                 Location = System.Web.UI.OutputCacheLocation.Client)]
    public ActionResult Get(string extenderTypeName) {

        if (string.IsNullOrEmpty(extenderTypeName)) {
            return new EmptyResult();
        }

        // Get the type representing the extender we are handling
        Type extenderType = toolkitAssembly.GetType(extenderTypeName,
                                                    false);
        if(extenderType == null) {
            return new EmptyResult();
        }

        // What other extenders does this one depend on?
        Stack<Type> dependencies = new Stack<Type>();
        AddDependencies(extenderType, dependencies);

        // What scripts do those extenders require?
        List<string> scriptsToInclude = new List<string>();
        GetDependencyScripts(dependencies, scriptsToInclude);

        return PartialView(scriptsToInclude);
    }

    // Find the types that the specified extender type depends on
    static void AddDependencies(Type extenderType,
                                Stack<Type> dependencies) {
        dependencies.Push(extenderType);
        Attribute[] attributes =
            Attribute.GetCustomAttributes(extenderType,
                      typeof(RequiredScriptAttribute));

        foreach (RequiredScriptAttribute attribute in attributes) {
            AddDependencies(attribute.ExtenderType, dependencies);
        }
    }

    // Find the scripts used by the specified extender types
    static void GetDependencyScripts(IEnumerable<Type> dependencies,
                                     ICollection<string> scripts) {
        foreach (Type dependency in dependencies) {
            Attribute[] attributes =
                Attribute.GetCustomAttributes(dependency,
                           typeof(ClientScriptResourceAttribute));

            foreach (ClientScriptResourceAttribute attribute in attributes) {
                if (!scripts.Contains(attribute.ResourcePath)) {
                    scripts.Add(attribute.ResourcePath);
                }
            }
        }
    }
}
}

The View is a User Control that simply outputs all of the required scripts.  This is the code-behind:

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Web.Mvc;

namespace TestDependencies.Views.ControlDependencies {
  public partial class Get : ViewUserControl<List<string>> {

    protected void Page_Load(object sender, EventArgs e) {
      Response.ContentType = "application/x-javascript";

      // Get compressed stream if possible
      Stream outputStream = GetOutputStream();

      using(StreamWriter outputWriter = new StreamWriter(outputStream)) {

        // Standard AJAX library
        string script = File.ReadAllText(
                              MapPath("/Scripts/MicrosoftAjax.js"));
        outputWriter.WriteLine(script);

        // Required scripts
        foreach(string scriptPath in ViewData.Model) {
          script = File.ReadAllText(MapPath("/Scripts/" + scriptPath));
          outputWriter.WriteLine(script);
        }

      }
      Response.End();
    }

    // Compress scripts if possible -- stolen from ToolkitScriptManager
    // in AJAX Control Toolkit
    private Stream GetOutputStream() {
      Stream outputStream = Response.OutputStream;
      if(!Request.Browser.IsBrowser("IE") ||
                         (6 < Request.Browser.MajorVersion)) {
        foreach(
          string acceptEncoding in (Request.Headers["Accept-Encoding"] ??
                                       "").ToUpperInvariant().Split(',')) {
          if("GZIP" == acceptEncoding) {
            Response.AddHeader("Content-encoding", "gzip");
            outputStream = new GZipStream(outputStream,
                                          CompressionMode.Compress);
            break;
          }
          if("DEFLATE" == acceptEncoding) {
            Response.AddHeader("Content-encoding", "deflate");
            outputStream = new DeflateStream(outputStream,
                                             CompressionMode.Compress);
            break;
          }
        }
      }
      return outputStream;
    }
  }
}

The ASCX is empty:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Get.ascx.cs" Inherits="TestDependencies.Views.ControlDependencies.Get"  %>

There are two advantages to using this technique.  Firstly performance should be increased since all the required scripts are returned in a single response.  Secondly you don't have to manually work out the dependencies for a particular ASP.NET AJAX Control Toolkit control.

Filed under: AJAX, MVC No Comments
7Sep/082

Using ASP.NET MVC and the AJAX Control Toolkit

There are a whole range of useful AJAX behaviors available in the ASP.NET AJAX Control Toolkit, and it would be cool to be able to use them from ASP.NET MVC. 

In this post I'll show you how you can use one of the extenders that I created, the ListSearch Extender, with ASP.NET MVC.

Set up your ASP.NET MVC Project

To start create a new ASP.NET MVC project.

Next download the Microsoft AJAX Library, which contains the Javascript library used in the Microsoft ASP.NET AJAX implementation. Copy the MicrosoftAjaxLibrary folder in the ZIP you downloaded to the Content folder in your MVC project.

Now download the Script Only Files from the ASP.NET AJAX Control Toolkit and copy the AjaxControlToolkit folder in the ZIP to the Content f0lder in your MVC project.

Now that the folders are copied you need to make them visible to the project.  Open the Content branch of your project in the Solution Explorer, and then click on the Show All Files button:

 

Once you do this, you should see the two folders that you copied into the Content folder.  Right click on each folder and select Include In Project.

Now your MVC project will have access to the JavaScript files it requires.

Add the SELECT

To make use of these JavaScript libraries, open the Home action's Index view under Views->Home->Index.aspx.  Modify the HTML to include a simple SELECT List:

<%@ Page Language="C#" ... %>

<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
    <h2> <%= Html.Encode(ViewData["Message"]) %></h2>
    <p>
        To learn more about ASP.NET MVC visit ...
    </p>

    <select id="Countries">
    <option>Switzerland</option>
    <option>United Kindom</option>
    <option>United States</option>
    </select>

</asp:Content>
 

Include the required Javascript files

Next you'll need to have the page pull in the JavaScript files that are used by the ListSearch Extender:

<script type="text/javascript" src="/Content/MicrosoftAjaxLibrary/System.Web.Extensions/3.5.0.0/3.5.21022.8/MicrosoftAjax.debug.js"/>
<script src="/Content/AjaxControlToolkit/3.0.20820.16598/3.0.20820.0/AjaxControlToolkit.Common.Common.js" type="text/javascript"></script>
<script src="/Content/AjaxControlToolkit/3.0.20820.16598/3.0.20820.0/AjaxControlToolkit.ExtenderBase.BaseScripts.js" type="text/javascript"></script>
<script src="/Content/AjaxControlToolkit/3.0.20820.16598/3.0.20820.0/AjaxControlToolkit.DynamicPopulate.DynamicPopulateBehavior.js" type="text/javascript"></script>
<script src="/Content/AjaxControlToolkit/3.0.20820.16598/3.0.20820.0/AjaxControlToolkit.Compat.Timer.Timer.js" type="text/javascript"></script>
<script src="/Content/AjaxControlToolkit/3.0.20820.16598/3.0.20820.0/AjaxControlToolkit.Animation.Animations.js" type="text/javascript"></script>
<script src="/Content/AjaxControlToolkit/3.0.20820.16598/3.0.20820.0/AjaxControlToolkit.Animation.AnimationBehavior.js" type="text/javascript"></script>
<script src="/Content/AjaxControlToolkit/3.0.20820.16598/3.0.20820.0/AjaxControlToolkit.PopupExtender.PopupBehavior.js" type="text/javascript"></script>
<script src="/Content/AjaxControlToolkit/3.0.20820.16598/3.0.20820.0/AjaxControlToolkit.PopupControl.PopupControlBehavior.js" type="text/javascript"></script>
<script src="/Content/AjaxControlToolkit/3.0.20820.16598/3.0.20820.0/AjaxControlToolkit.ListSearch.ListSearchBehavior.js" type="text/javascript"></script>

 
You may need to change the version numbers depending on the versions of the Microsoft AJAX library and Toolkits you downloaded.
 
I found out which script files to include by creating a normal ASP.NET Project that used the ListSearch Extender, and set the ScriptManager's ScriptPath property to a new folder I created in the project called /MicrosoftAjaxLibrary, to which I copied the same two folders that I copied to the Content folder above.  I accessed the page, and did a View Source to see what JavaScript was being used.
 

Initialize the Extender

Having included the required JavaScript files, you can now create a new ListSearch Extender and attach it to the SELECT you created above:

<script type="text/javascript">
    Sys.Application.initialize();
    Sys.Application.add_init(function() {
    $create(AjaxControlToolkit.ListSearchBehavior,
            { "id": "ListBox1_ListSearchExtender" },
            null, null, $get("Countries"));
    });
</script>
 

Access your page

 
Now when you run you'll get the ListSearch Extender behavior attached to your SELECT:
 

Summary

 
Using the AJAX Control Toolkit is very straight-forward.  The trickiest part is determining exactly what script files you need to include.
 
Now you can use the whole set of behaviors that other people have created to add AJAX goodness to your ASP.NET MVC projects.
Filed under: AJAX, MVC 2 Comments
13Aug/076

Dealing with "Sys.InvalidOperationException: Handler was not added through the Sys.UI.DomEvent.addHandler method."

In the ListSearch extender I recently had a bug where you got an exception when leaving a page that had a ListBox on it, where the ListBox was the target of both a CascadingDropDown extender and a ListSearch extender.

The reason was pretty dumb-- I was calling $clearHandlers(listBox) in the dispose method on the ListSearch Extender, and this was removing the handlers that the CascadingDropDown had set up.  So when the CascadingDropDown's dispose method tried to clean up its event subscriptions, the ASP.NET AJAX runtime complained that it was not subscribed.

The solution was replace calls to $addHandlers and $clearHandlers with individual calls to $addHandler and $removeHandler for each individual event in the ListSearch's initialize and dispose methods.

Unfortunately after implementing this solution I still got the same error ... after a lot of head-scratching, it turned out to be a simple typo.  I was calling $addHandler with 'keydown' as the event name, and calling $removeHandler with 'keyDown' as the event name.

A couple of things for you to check if you get the same error.

Filed under: AJAX 6 Comments
26Jun/071

Dynamically creating ASP.NET AJAX controls

In my previous post I showed how you can cache large HTML elements, such as SELECTs in the web browser's document cache.  What if you want to dynamically create and attach ASP.NET AJAX Control Extenders to the dynamically created SELECT?

I've already shown how you can call extenders from JavaScript, but if you want to create and delete them from scratch, you can use functions such as these:

function AddListSearchTo(list) {
    list.listSearch = $create(AjaxControlToolkit.ListSearchBehavior,
                              { 'promptCssClass' : 'listSearchPrompt' },
                              {}, {}, list);
}

function RemoveListSearchFrom(list) {
    if(list.listSearch ) {
        list.listSearch .dispose();
        list.listSearch = null;
    }
}

I use an expando on the target SELECT to point to the ListSearch Extender that I've dynamically created, which means that I can easily get hold of it if I want to remove it. 

As you can see you can easily pass initial properties too.  The $create function is a shortcut to Sys.Component.create

I use these functions when dynamically fetching HTML SELECTs from the server, in response to user input -- I've rolled my own CascadingDropDown. When I get the new SELECT back from the server the first thing I do is remove the existing extender from the existing SELECT, then I replace the old SELECT with the new one that I've just received, and then I dynamically instantiate a new ListSearch extender:

function OnGetListSucceded(list) {
    var theListDiv = $get('TheListDiv'); // Contains TheList
    var theList = $get('TheList');       // An HTML SELECT
    RemoveListSearchFrom(theList);

    theListDiv.innerHTML = list;
    theList = $get('TheList');
    AddListSearchTo(theList);
}
Filed under: AJAX 1 Comment
24Jun/072

Caching large HTML elements in the Browser’s document cache

Sometimes we have to do things that we know are wrong.

For example, in my current project we have to present a list of customers so that the user can select one or more of them.  I work for a large company that has been around for many many years, and the list of customers can exceed 15,000 for some countries.

But I can't decide which ones should be shown, and which ones should not be shown.  Instead I need to show the complete list to the user.   What is painful is that it can take a bit of time for this list to be sent down to the browser.  It would be much better would be if the customer list could be cached on the browser's document cache.  This is what this blog post is about.

Note that in order for this to work, the list is presented as a simple HTML SELECT, and the selected items need to be processed correspondingly on the server using the Request.Form[SelectName] property.

In the ASPX page, I define my customer list like this: 

<div id="ListDiv">
    <select id="List" multiple="multiple" size="15" disabled="disabled">
        <option>Loading List ...</option>
        <option>This may take a moment the first time...</option>
    </select>
</div>

Note that the SELECT is surrounded by a DIV, so that I can replace the DIV contents with a new list.

Then I have some JavaScript that fires when the page loads, and asynchronously fetches the list:

<script language="javascript" type="text/javascript">
// This is called by the ASP.NET AJAX Framework automatically
function pageLoad() {
    var wRequest =  new Sys.Net.WebRequest();
    wRequest.set_url('ListHandler.ashx?Version=<%=ListVersion.Value%>)');
    wRequest.set_httpVerb("GET"); // GETs can be cached, POSTs can not
    wRequest.add_completed(OnFetchListCompleted);
    wRequest.invoke();
}

The pageLoad function gets invoked automatically.  It simply fires off an HTTP GET request to a handler, passing as a parameter the value of the ListVersion hidden field.  By changing the value of this field, the server-side code can control when the browser-cached contents are expired, and a new version is fetched (since changing the URL will mean there is no browser-cached version).

When the response comes back, it replaces the contents of the DIV with the value that is returned from the handler:

function OnFetchListCompleted(executor, eventArgs)
{
    var listDiv = $get('ListDiv');

    if(executor.get_responseAvailable())
    {
        var list = executor.get_responseData();
        if(list && list.startsWith('<select')) {
            listDiv.innerHTML = list;
        }
    }
}
</script>

The handler simply generates the appropriate content:

<%@ WebHandler Language="C#" Class="ListHandler" %>

using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Collections.Generic;

public class ListHandler : IHttpHandler {

    public void ProcessRequest (HttpContext context) {
        context.Response.Clear();
        context.Response.ContentType = "text/html";
        context.Response.Cache.SetCacheability(HttpCacheability.Public);
        context.Response.Cache.SetExpires(DateTime.Now.AddMonths(1));
        context.Response.Cache.SetSlidingExpiration(true);

        HtmlTextWriter htmlTextWriter = new HtmlTextWriter(
            context.Response.Output);
        ListBox listBox = GetListBox();
        listBox.RenderControl(htmlTextWriter);
        htmlTextWriter.Flush();

        context.Response.End();
    }

    private static ListBox GetListBox()
    {
        ListBox listBox = new ListBox();
 ...
        return listBox;
    }
... 

Note the bolded directives to enforce the browser-side caching of the response.

I have a complete example here.

You might be wondering why I've used the Sys.Net.WebRequest mechanism with the HttpHandler.  Why not simply use a Web Service? That would indeed be simpler, however there is no easy way to set the appropriate cache expiration headers, although it is possible if you are willing to use reflection -- the PageFlakes guys use it to speed up their pages.

Filed under: AJAX 2 Comments
19Jun/075

Speeding Up ListSearch Extender initialization with massive lists

When a ListBox that has an associated ListSearch Extender is first used, the ListSearch Extender does some one-time initialization.

It delays this work this for very good reasons, as Ted Glaza explains here.

The extender needs to decide whether it can use the very fast binary search when the user types, or whether it must use the slower linear search.  It does this by determining if the list is sorted, which can take a long time if there are thousands and thousands of entries in the ListBox.

The best thing would be to somehow give the ListSearch Extender a hint as to whether the ListBox is already sorted or not.

You can do this by making a couple of changes to the ListSearch extender.  If you'd like to see this behaviour become a standard part of the ListSearch Extender, please vote for it and I'll add it if there is sufficient interest.

Modifying the ListSearch Extender is extremely simple.  First download the AJAX Control Toolkit, and then open the AjaxControlToolkit.sln

Open the behaviour JavaScript file "ListSearchBehavior.js".  At the top of the file you'll see some properties.

Add a couple of new property:
    // Properties
    this._isSortedHintSet = false;
    this._isSortedHint = false;
...

These indicate whether the user has told the extender if the list is sorted or not, and if so, whether it is sorted.

Next find the "_isSorted" function, and at the start of it (after the documentation comment), add this code:

if(this._isSortedHintSet) {
    return this._isSortedHint;
}
...

Finally you need to expose this property so that it can be set from outside the class, although you could actually set the  _isSortedHintSet and _isSortedHint from JavaScript, this is considered very bad form since the underscore prefix indicates they are private.

Define a getter and setter accessor at the end of the bevahior, where you'll find the other accessors:
    get_isSortedHint : function() {
        /// <value type="AjaxControlToolkit.ListSearchPromptPosition">
        /// Where the prompt should be positioned relative to the target control.
        /// Can be Top (default) or Bottom
        /// </value>
        return this._isSortedHint;
    },

    set_isSortedHint : function(value) {
        if (!this._isSortedHintSet || this._isSortedHint != value) {
            this._isSortedHintSet = true;
            this._isSortedHint = value;
            this.raisePropertyChanged('isSortedHint');
        }
    }

If you add these two functions after the existing last function (which is "set_raiseImmediateOnChange" in the current build), then you must be sure to add a comma after the closing brace of this last function:
...
    },

    get_isSortedHint : function() {
....

Build the solution, and make sure projects that use the AJAX Control Toolkit now reference the AjaxControlToolkit.dll in AjaxControlToolkit\AjaxControlToolkit\bin\Debug

Now you can set this property in JavaScript when your page first loads, so that the extender does not have to work out whether the list is sorted:

    <form id="form1" runat="server">
        <asp:ScriptManager ID="SM1" runat="server" />

        <asp:ListBox ID="LB1" runat="server">
            <asp:ListItem>Hello</asp:ListItem>
        </asp:ListBox>
        <cc1:ListSearchExtender ID="LSE1" runat="server"
            TargetControlID="LB1"></cc1:ListSearchExtender>

        <script language="javascript" type="text/javascript">
            function pageLoad(sender, args) {
                var extender = $find('LSE1');
                if(extender) {
                    extender.set_isSortedHint(true);
                }
            }
        </script>
    </form>

This is the complete code (no changes to the code-behind file).

The pageLoad function is called automatically by the ASP.NET AJAX Framework (see http://ajax.asp.net/docs/overview/AJAXClientEvents.aspx) when all controls within the page have been loaded.

The function first finds the ListSearch Extender, and then gives it a hint that the list that it is targeting is actually sorted.

If you wanted to be able to set the property from the server side, instead of from JavaScript, you can edit the ListSearchExtender.cs file, and add the appropriate declaration of the property:

        [ExtenderControlProperty]
        [ClientPropertyName("isSortedHint")]
        [DefaultValue("")]
        public string IsSortedHint
        {
            get { return GetPropertyValue("isSortedHint", ""); }
            set { SetPropertyValue("isSortedHint", value); }
        }
 
Filed under: AJAX 5 Comments
19Jun/0711

Hiding the ListSearch Extender Prompt Message

You can hide the ListSearch Extender's prompt message, so that it does not show, by setting the style to "display:none", like this.

Define a CSS class in a StyleSheet"StyleSheet.css":
.DontShowListSearchPrompt
{
    display:none;
}

Then in your ASPX use the Style for the ListSearch Prompt:

<head runat="server">
    <title>Don't show ListSearch Extender Prompt</title>
    <link href="StyleSheet.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="SM1" runat="server" />

        <asp:ListBox ID="LB1" runat="server">
            <asp:ListItem>Hello</asp:ListItem>
        </asp:ListBox>
        <cc1:ListSearchExtender ID="LSE1" runat="server"
                           PromptCssClass="DontShowListSearchPrompt"
                           TargetControlID="LB1">
        </cc1:ListSearchExtender>
    </form>
</body>
</html>

Filed under: AJAX 11 Comments
28Feb/070

Automatically attaching Extenders to Controls using Control Adapters – demo posted

I get a lot of hits on this article I posted last year, showing how you can use ASP.NET Control Adapters to automatically attach Control Extenders to a specific kind of ASP.NET control throughout an existing web site, without modifying any of the existing pages.

It uses a Control Adapter to create and attach extenders to the target controls, and also an HttpModule to ensure that all pages have a ScriptManager or ScriptManagerProxy.

Anyway, I finally got around to uploading a demo project showing this.  It is slightly different to the article, since it shows how to attach a DropShadow extender to all UpdatePanels on your site.  Not something you will necessarily want to do, but you get the point.

The demo project is here.  It consists of a class project containing the Control Adapter and Http Module, and then a sample web site with a .browsers file to cause the adapter to be used, and a modified web.config to let the Http Module do its stuff.

Filed under: AJAX No Comments
28Feb/0710

Calling AJAX Control Extenders from JavaScript code

I am a big fan of declarative programming.  It reduces the risk of errors, saves on mundane coding and lets me say what I want to do and someone else can figure out the how (although I like to understand the how too :-).

That said, when you find yourself doing strange and unnatural things simply to be able to use declarative programming then you know you've gone too far.

I found myself in this kind of situation recently.  I have a very large GridView and for one of the columns I wanted to display a ModalPopup whenever the user clicked on the corresponding item in each row, where they could select information related to that item.

What I could have done is to hook up an instance of the ModalPopup in the template, so that for each row a new ModalPopup would be created.

Instead what I did was to define a single ModalPopup outside of the GridView, targeted at a dummy hidden LinkButton (with no label):

<asp:LinkButton ID="DummyHiddenLinkButton" runat="server"></asp:LinkButton>

<cc1:ModalPopupExtender ID="SelectYxzModalPopupExtender"
                                                         
runat="server" 
                                                         
TargetControlID="DummyHiddenLinkButton"
                                                         
PopupControlID="SelectYxzPanel" 
                                                         
OkControlID="SelectYxzOKButton" >
</cc1:ModalPopupExtender>

Then in the template I hooked up the onclick method of the item to invoke a JavaScript function  passing as a parameter key information related to the item.

Inside my JavaScript function I first initialize the Panel with information related to the item (in my case by invoking a page method), and then I programatically cause the ModalPopup to show:

function OnItemLinkClicked(someId) {
    PageMethods.SomePageMethod(someId,
                               OnSomePageMethodSucceded,
                               OnSomePageMethodFailed);
}

function OnSomePageMethodSucceded(result) {
    var extender = $find('SelectYxzModalPopupExtender');
    var someList = $get('SomeListInTheModalPanel');
    if(!extender || !someList) { ...
    }
    someList.options.length = 0;
    for(var i = 0; i < result.length; i++) {
        var option = document.createElement('OPTION');
        option.text = result[i].Text;
        option.value = result[i].Value;
        someList.options.add(option);
    }
    extender.show();
}

I've shown my call to a backend page method for completeness. I'm invoking a page method to get an array of structs related to the item that the user clicked, and then using the resulting array to populate a SELECT that I display within the modal popup.

But the interesting stuff is in bold. Here I am programatically finding my ModalPopup extender (using the $find shortcut to the Sys.Application.findComponent method), and then invoking its show method to cause it to display. The hidden LinkButton that is its "official" target never actually gets used.

I discovered what methods were available on the ModalPopup extender by looking at its source, which is available for all the control extenders when you download the AJAX Control Toolkit. Methods that are prefixed with an underscore are internal methods and I'd never call those, but I'm pretty comfortable calling the other methods, such as the handy show method above.

Filed under: AJAX 10 Comments
25Feb/078

Screencast: Introduction to AJAX, ASP.NET AJAX and the AJAX Control Toolkit

Last Tuesday I gave a presentation in Geneva on ASP.NET AJAX and the AJAX Control toolkit, and this morning I created a one hour screencast of the presentation available here (25MB) zipped, or here (33MB) as a WMV.

I tried to make it useful for someone that has at least dabbled in ASP or ASP.NET, and wants a quick bootstrap into Microsoft's AJAX offerings.

 It includes introductions to:

  • AJAX
  • ASP.NET AJAX including coding demos of the UpdatePanel (including a look behind the scenes), Web Services, and the JavaScript extensions
  • The AJAX Control Toolkit including coding demos of using a couple of the control extenders
  • Creating AJAX Control Extenders including a coding demo of creating your own extenders

It is encoded at quite a high level of compression so the sound is a little distorted but perfectly understandable.

Note:I recorded this around 6am this morning, and around 55 minutes into it my three year old and five year old sons decided to make their own (very sweet) contributions to the screencast -- please forgive my momentary distraction, and when you hear me tell my three year old that he can take the money he found: he is  talking about two small coins I gave him yesterday.  This is not an attempt at bribary (honest!).

Many thanks to Atif Aziz and Dominique Kuster for organizing last week's presentation, and Dicomp Acadamy Suisse Romande for hosting it.

Filed under: AJAX, Screencast 8 Comments