Mike Nichols - Son of Nun Technology

AutoSuggestBox ASP.NET Control With Scriptaculous Autocompleter

A while back I posted some findings while searching for a suggest box that was easy to use and bore the weight of javascripting for me. I extended a decent control I found on CodeProject but the problem was that it subclassed Anthem.NEt's TextBox control. What I wanted was to rather have an extender-type of control that would decorate the behavior of any ITextControl derivative so that I could freely use third-party control and such.

Going through all this made me really want to get familiar with prototype and Script.aculo.us libraries. They are widely used and the Ajax Control Toolkit has been buggy for me to use so I thought it would be a good opportunity to really get under the hood of what AJAX is and how to deal with out-of-band requests.

Here were my requirements:

1. Plays nice with the UpdatePanel in ASP.NET Ajax Web Extensions...no kooky parser errors

2. Templatable for Header, Item, and EmptyItem situations

3. Easy to style

4. Pluggable script options so I can have other client-side actions respond to selections

5. SelectedValue property and SelectedValueChanged event on client-side and server-side

6. Options for Animated gifs for progress indicators and I can put those ANYWHERE on the page

7. Can have the suggestions set at a width different from the referenced TextBox (which is the default behavior for the Scriptaculous control)

Before building the server control needed to wrap my head around the prototype.js library while digging into the excellent Scriptaculous autocompleter control. These libraries make all kinds of things a snap and are a blast to play around with. I am learning monorail at the same time so I needed to understand prototype anyways.

Along the way I came across Simone Busoli's BusyBox implementation of the Scriptaculous control and while it had many features I needed, I wanted to extend it further. 

Here's a look at what I can do now: 

        <asp:TextBox runat="Server" ID="TextBox1" Width="400px"/><asp:PlaceHolder runat="server" ID="ImagePlaceHolder1" /><br />

        <SON:AutoSuggester runat="server" ID="AutoSuggester1" TargetControlID="TextBox1"

                ProgressAnimatedImage="AzureFlower" Width="650px"

                AnimatedImageContainerID="AnimatedImageContainer1" OnSelectedValueChangedClientFunction="refreshUpdatePanel">

            <HeaderTemplate>

                Header Stuff Here

            </HeaderTemplate>

            <ItemTemplate>

                <%# Container.DataItem %>

                <span>Man we should make this complicated</span><br />

                <asp:TextBox ID="TextBox2" runat="Server" >For Instance here is a textbox</asp:TextBox>

            </ItemTemplate>

            <EmptyItemTemplate>No Items Found!</EmptyItemTemplate>

        </SON:AutoSuggester>

  • TargetControlID - The ID (in the same Naming Container) of the TextBox to attach behavior to
  • ProgressAnimatedImage - A enumeration of stock images to show while the callback is happening
  • AnimatedImageContainerID - The control that will hold the image...monkeying around with Response.Filters to render the image html right after the TextBox caused too many parser headaches in UpdatePanels
  • OnSelectedValueChangedClientFunction - A client side function that will receive one parameter (the new value of the hidden _value field). You can use this to refresh update panels too or whatever
  • HeaderTemplate - A non-selectable item that is, um , the header
  • ItemTemplate - The template just like in a Repeater that you can use to show other bits of info, but perhaps just populating the textbox with a specific field (see DataTextField)
  • EmptyItemTemplate - The template that will show (along with the Header if specified) when no items are returned on the callback
  • DataTextField (not shown) - The property name in the DataItem to reflect upon for the TEXT WHICH WILL POPULATE THE TEXT BOX
  • DataValueField(not shown) - The property name in the DataItem to reflect upon for the value that will be saved as the SelectedValue (hidden field)

 

The html for the items rendered by the Items are as follows:

<ul>

    <li id='1' class='autosuggester-item' >

        <span style='display:none;'>One</span>

        <div class='informal' >One<input type='hidden' id='AutoSuggest1_Results_1' value='One'/></div>

    </li>

    <li id='2' class='autosuggester-item' >

        <span style='display:none;'>Two</span>

        <div class='informal' >Two<input type='hidden' id='AutoSuggest1_Results_2' value='Two'/></div>

    </li>

    <li id='3' class='autosuggester-item' >

        <span style='display:none;'>Three</span>

        <div class='informal' >Three<input type='hidden' id='AutoSuggest1_Results_3' value='Three'/></div>

    </li>

</ul>

The 'informal' class is used by the Scriptaculous script to identify items that should not populate the TextBox. The first hidden span is the actual value that will be placed in the TextBox.

While there are a few more features I want to implement I think this is pretty rich and I have it working inside UpdatePanels and multiple controls and so on. It's fairly lightweight, too, as are the Scriptaculous and prototype libraries that are embedded.

I don't have a binary dist yet so you can hop over to my new google hosted source-code repository at http://son-of-nun.googlecode.com/svn/ .

On a side note, it was somewhat frustrating not being able to manipulate the rendered HTML from another control (the TargetControl here) without making ASP.NET go awry. It seems like it would be simple to have an event that fires pre- and post- Render() method that passes the html so it could be played with. But this is probably another case of protecting the developer from the framework.

Apparently this is a known issue when you use Response.Write directly in your code during callbacks. I implemented a custom Response.Filter to intercept the html during the Write() call and injected the image html there, but while this worked great on partials where no callback was taking place, the UpdatePanel simply couldn't figure out what was going on and would complain.

Enjoy...

Resources